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 
<http://docs.libuv.org/en/v1.x/loop.html#c.uv_update_time>, 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.

There are cases, however, where telling libuv to update the time isn't 
practical.  One such case would be node itself.  Given this code:

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.

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.

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 
<https://github.com/libuv/libuv/blob/master/src/unix/core.c#L297> 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.

-- 
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.

Reply via email to