Skip to content

StreamSelectLoop: Timers with very small intervals do not work correctly #48

@joshdifabio

Description

@joshdifabio

Within StreamSelectLoop::run() is the following code:

} elseif ($scheduledAt = $this->timers->getFirst()) { // float
    $timeout = $scheduledAt - $this->timers->getTime(); // float - float
    if ($timeout < 0) {
        $timeout = 0;
    } else {
        $timeout *= self::MICROSECONDS_PER_SECOND; // float * int
    }
}

In the case of a periodic timer with an interval equal to Timer::MIN_INTERVAL (0.000001), the above code, in conjunction with the Timers class, essentially does something like the following:

$currentTime = microtime(true);
$ourTimerInterval = 0.000001;
$nextTimerScheduledAt = $currentTime + $ourTimerInterval;
// $timeout is for stream_select()/usleep() call
$timeout = $nextTimerScheduledAt - $currentTime; // This is NOT equal to 0.000001 due to float error [1]
$timeout *= 1000000; // This looks like it should be (int)1, but it is actually approximately (float)0.95

$timeout is then passed to stream_select() and usleep(), both of which expect int and so cast (float)0.95 to (int)0, not waiting at all when the timer was intended to delay for 1 microsecond. Furthermore, the fix in #37 is bypassed when this issue occurs as (float)0.95 casts to boolean true.

A quick fix would be to apply rounding to $timeout *= self::MICROSECONDS_PER_SECOND;.

[1] https://3v4l.org/FvioX

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions