Hi there,
> On Jun 21, 2017, at 16:12, 'Daryl Haresign' via libuv
> <[email protected]> wrote:
>
> My post is a question about the statement:
>
> libuv caches the current time at the start of the event loop tick in order to
> reduce the number of time-related system calls.
>
> I think that libuv should additionally update the loop time if it calculates
> the poll timeout based on pending timers.
>
> Below is what turned out to be a rather lengthy description of the issue with
> how things are now, and a number of options to solve the issue.
>
> If the callback of a repeating timer takes a reasonable amount of time to
> execute, it will skew the timer by the amount of time it took. This is
> somewhat documented here, and the recommended fix is to call
> uv_update_time(loop) if any of your callbacks take a long time. Adding this
> at the end of the callback indeed removes the skew.
>
Correct.
> There are cases, however, where telling libuv to update the time isn't
> practical. One such case would be node itself. Given this code:
>
Well, Node is using libuv timers in a special kinda way: it keeps one libuv
timer per Node timer bucket (for a given timeout). In addition, if you are
blocking on a timer you are inherently doing it wrong.
> var spin = (duration) => {
> var start = Date.now();
>
> while (Date.now() - start < duration) {
> // spin
> }
> };
>
> var start = Date.now();
> var calls = 0;
> var id = setInterval(() => {
> console.log((Date.now() - start) + ": interval " + ++calls);
>
> spin(15);
>
> if (5 === calls) {
> clearInterval(id);
> }
> }, 100);
>
> nodejs prints:
>
> $ node timer.js
> 120: interval 1
> 240: interval 2
> 359: interval 3
> 478: interval 4
> 593: interval 5
>
> node clearly doesn't know the callback is going to take a long time, and I
> doubt there's a way to inform node from JavaScript.
>
> Having taken a look at the relevant code in libuv, my understanding of the
> problem is the following:
>
> loop time | wall time | event
> ----------+-----------+---------------------------------------------------------
> 0 | 0 | the event loop starts
> 0 | 0 | loop time updated
> 0 | 0 | the interval timer is scheduled for t = 100
> 0 | 0 | as nothing else is going on, libuv asks the timer
> system
> | | for the next timeout, 100, and polls for io with
> | | timeout = (100 - 0)
> 0 | 0 | polls for io
> 0 | 100 | no io happened, so polling timed out
> 0 | 100 | next loop iteration
> 100 | 100 | loop time updated
> 100 | 100 | timer is due, timer rescheduled for t + 100, callback
> | | called
> 100 | (!) 115 | callback done
> 100 | 115 | as nothing else is going on, libuv asks the timer
> system
> | | for the next timeout, 200, and polls for io with
> | | timeout = (200 - 100)
> 100 | 215 | no io happened, so polling timed out
> 100 | 215 | next loop iteration
> 215 | 215 | loop time updated
> 215 | 215 | timer is due, timer rescheduled for t + 100, callback
> | | called
> 215 | (!) 230 | callback done
> ... | ... | ...etc...
>
> So it seems that the root cause of the issue is that libuv is miscalculating
> the poll timeout when nothing else is going on. Now one potential fix for
> someone trying to use libuv might be to have a uv_prepare_t handle which
> calls uv_update_time(loop) thus ensuring that the loop time is correct at the
> point that it asks the timer system for the next timeout. The downside of
> this is that it's called for every loop iteration, regardless of whether the
> poll timeout would have been special cased to 0.
>
No, the root cause is the user is blocking in a timer callback. As you already
found, this is all documented behavior.
> A downside of the recommendation of the docs, stating that you should call
> uv_update_time(loop) if your callback takes a long period of time, is that if
> I have more than one long callback I may end up calling it multiple times per
> loop iteration.
>
Since Node uses a single timer for more than one JS timers, it would be
implemented at the Node level.
> An alternative idea, is a change to libuv: if it decides it should ask the
> timer system for the next timeout, it should first update the loop time. In
> other words, this method would be changed to call uv_update_time(loop) before
> calling uv__next_timeout(loop). The benefit of this is that the time-related
> system call is delayed until necessary (I think at this point it is
> necessary), and only once per loop. If a bunch of special cases in that
> method determine that the poll timeout should be 0, the system call is not
> made. Users no longer need to inform libuv if they think that their
> callbacks will be long running. node timers stop being skewed.
>
That would mean that we update the loop time at least twice per loop iteration,
which is what we wanted to avoid in the first place.
Note that timers will run late if you decide to block on *any* callback really.
The programming model based on an event loop has this inherent limitation, and
your suggestion would just be a bandaid for a single use case, which is timers.
libuv caters to a larger audience than Node, and blocking in a check callback
would also make timers run late, which cannot be addressed, so personally, I
don’t see myself supporting this change.
Cheers,
--
Saúl
--
You received this message because you are subscribed to the Google Groups
"libuv" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/libuv.
For more options, visit https://groups.google.com/d/optout.