Skip to content

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

Closed

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

Activity

  1. enumag commented on Oct 31, 2016

    @enumag

    I'm having trouble with periodic timers with period lesser than 1 (1.0 is ok, 0.999 is not) and StreamSelectLoop (didn't try other loops yet). I'm stopping the loop after 1.5 second. With period set to 1 the timer is executed once. With period set to 0.999 it's executed several thousands times.

    The suggested fix did not help at all.

  2. kelunik commented on Nov 16, 2016

    @kelunik
    Contributor

    Having a timer executed every microsecond is the first mistake. What's the purpose of that timer?

    Apart from that I'm glad that we have int for async-interop/event-loop instead of floats.

  3. enumag commented on Nov 16, 2016

    @enumag

    I don't want timer executed every microsecond, just every half-second or so - for faster tests execution. But instead of being executed twice in one second it is executed like 3000 times.

    Also how can I use the async-interop/event-loop library? There is no non-abstract driver implementation.

  4. kelunik commented on Nov 16, 2016

    @kelunik
    Contributor

    @enumag Amp will support it in version 2.0.0, currently in development. @WyriHaximus started to build an adapter for React as well.

  5. clue commented on Nov 16, 2016

    @clue
    Member

    @enumag Have you tried the suggested fix in #49 and this did not resolve your issue?

    Can you post a gist that helps us reproduce the issue you're seeing?

  6. ivkalita commented on Mar 20, 2017

    @ivkalita
    Contributor

    @clue, hi! Do you have any idea about how to test this thing? I stuck with the same problem as @joshdifabio in #49 – it's hard to test that this feature is working. We need to run timer with very-very small interval and loop's time cost itself is much more expensive and takes much more time than usleep(1).

  7. jsor commented on Apr 4, 2017

    @jsor
    Member

    Fixed in #93

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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