2

UPD: The question What is the reason JavaScript setTimeout is so inaccurate? asks why the timers in JavaScript are inaccurate in general and all mentions of the inaccuracy are about invocations slightly after the specified delay. Here I'm asking why NodeJS tolerates also invocations even before the delay? Isn't it an error-prone design of the timers?

Just found an unexpected (to me only?) behaviour of NodeJS setTimeout(). Some times it triggers earlier than the specified delay.

function main() {
  let count = 100;
  while (count--) {
    const start = process.hrtime();
    const delay = Math.floor(Math.random() * 1000);

    setTimeout(() => {
      const end = process.hrtime(start);
      const elapsed = (end[0] * 1000 + end[1]/1e6);
      const dt = elapsed - delay;
      if (dt < 0) {
        console.log('triggered before delay', delay, dt);
      }
    }, delay);
  }
}

main();

On my laptop output is:

$ node --version
$ v8.7.0

$ node test.js
triggered before delay 73 -0.156439000000006
triggered before delay 364 -0.028260999999986325
triggered before delay 408 -0.1185689999999795
triggered before delay 598 -0.19596799999999348
triggered before delay 750 -0.351709000000028

Is it a "feature" of the event loop? I always thought that it must be triggered at least after delay ms.

Ivan Velichko
  • 6,348
  • 6
  • 44
  • 90
  • I've replicated the issue. I tried to put the `setTimeout(...)` inside an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) like this `(function(start, delay) { ... })(start, delay);` and it seems to reduce the number of events triggered before expected. But I still haven't an explanation. – raul.vila Apr 17 '18 at 13:10
  • The delay is short by fractions of a millisecond. I think that's about as good as you're going to get. – Pointy Apr 17 '18 at 13:11
  • duplicate? https://stackoverflow.com/q/21097421/9083959 – lukas-reineke Apr 17 '18 at 13:11
  • The motivation behind the IIFE is the same as in [this question](https://stackoverflow.com/questions/49836498/add-several-event-listeners-to-table/49836537#49836537). – raul.vila Apr 17 '18 at 13:13
  • 2
    Possible duplicate of [What is the reason JavaScript setTimeout is so inaccurate?](https://stackoverflow.com/questions/21097421/what-is-the-reason-javascript-settimeout-is-so-inaccurate) – raul.vila Apr 17 '18 at 13:13
  • Why it's duplicate? The former question and corresponding answers mention only invocation of the callback after the specified delay while in my example the callback is invoked earlier. – Ivan Velichko Apr 17 '18 at 13:18
  • @IvanVelichko see answer marked as solution of the linked question, it shows the case of Chrome executing the callback before the timeout. – raul.vila Apr 17 '18 at 13:30
  • 3
    Why is this voted down? The question is perfectly valid. I just ran into a broken test case because it expected the timeout to be **at least** the specified count of milliseconds. I did not recignize this mistake at first. – Peopleware Oct 22 '19 at 11:44

2 Answers2

3

From the NodeJS docs:

The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.

As you increase the number of intervals (you have 100) the accuracy decreases, e.g., with 1000 intervals accuracy is even worse. With ten it's much better. As NodeJS has to track more intervals its accuracy will decrease.

We can posit the algorithm has a "reasonable delta" that determines final accuracy, and it does not include checking to make sure it's after the specified interval. That said, it's easy enough to find out with some digging in the source.

See also How is setTimeout implemented in node.js, which includes more details, and preliminary source investigation seems to confirm both this, and the above.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • 2
    I don't expect it to be called exactly after `delay` ms. I just expect it to be called *after* `delay`. But in my example it's called before. – Ivan Velichko Apr 17 '18 at 13:14
  • The expresion "as close as possible to the time specified" does not imply after specified time. It could be before. The [question linked in the comments](https://stackoverflow.com/q/21097421/9083959) provides more info. – raul.vila Apr 17 '18 at 13:17
  • @IvanVelichko "As close as possible" carries no implications regarding before/after, though. IMO inaccuracies are to be expected in such an environment. Also consider you're loading up the number of intervals; with a lower number of intervals the inaccuracy is significantly less, with more intervals it's much worse. As it has to check more intervals its accuracy will decrease, and whatever algorithm it uses to see if an interval is "close enough" will determine final accuracy. – Dave Newton Apr 17 '18 at 13:21
-1

From node timers doc, about settimeout:

The only guarantee is that the timeout will not execute sooner than the declared timeout interval

a.v
  • 1
  • 2
    And the question is "why is this guarantee broken?" – Kaiido Jan 01 '23 at 01:13
  • I've got a failure where it's firing .55ms early, which for a timeout of 3ms is quite a substantial error. – Jason Feb 27 '23 at 21:26
  • I've ended by adding/removing 1ms here and there because of potential rounding errors, which had marginally helped,and finally globed the functions to play in a selfmade settimeout which calculate the date at which the functions are supposed to run and when that time happens checks if date has passed with a superb "while date < target date { wait(Nms) }" ....now i'm a bit late, but never too early.. – a.v Mar 02 '23 at 21:42