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.
