Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does node.js handle setTimeout(func, 1.0) incorrectly?

Tags:

node.js

v8

While working on a timing sensitive project, I used the code below to test the granularity of timing events available, first on my desktop machine in Firefox, then as node.js code on my Linux server. The Firefox run produced predictable results, averaging 200 fps on a 1ms timeout and indicating I had timing events with 5ms granularity.

Now I know that if I used a timeout value of 0, the Chrome V8 engine Node.js is built on would not actually delegate the timeout to an event but process it immediately. As expected, the numbers averaged 60,000 fps, clearly processing constantly at CPU capacity (and verified with top). But with a 1ms timeout the numbers were still around 3.5-4 thousand cycle()'s per second, meaning Node.js cannot possibly be respecting the 1ms timeout which would create a theoretical maximum of 1 thousand cycle()'s per second.

Playing with a range of numbers, I get:

  • 2ms: ~100 fps (true timeout, indicating 10ms granularity of timing events on Linux)
  • 1.5: same
  • 1.0001: same
  • 1.0: 3,500 - 4,500 fps
  • 0.99: 2,800 - 3,600 fps
  • 0.5: 1,100 - 2,800 fps
  • 0.0001: 1,800 - 3,300 fps
  • 0.0: ~60,000 fps

The behavior of setTimeout(func, 0) seems excusable, because the ECMAScript specification presumably makes no promise of setTimout delegating the call to an actual OS-level interrupt. But the result for anything 0 < x <= 1.0 is clearly ridiculous. I gave an explicit amount of time to delay, and the theoretical minimum time for n calls on x delay should be (n-1)*x. What the heck is V8/Node.js doing?

var timer, counter = 0, time = new Date().getTime();

function cycle() {
    counter++;
    var curT = new Date().getTime();
    if(curT - time > 1000) {
        console.log(counter+" fps");
        time += 1000;
        counter = 0;
    }
    timer = setTimeout(cycle, 1);
}

function stop() {
    clearTimeout(timer);
}

setTimeout(stop, 10000);
cycle();
like image 502
HonoredMule Avatar asked Feb 15 '12 04:02

HonoredMule


People also ask

Why setTimeout is not working in JS?

This is due to when a function is executed as a parameter to setTimeout , the execution context is different to the execution context of the function! Now this will print out undefined because the this keyword is executed in the context of the setTimeout function and is therefore not defined.

Does setTimeout work in node JS?

setTimeout() can be used to schedule code execution after a designated amount of milliseconds. This function is similar to window. setTimeout() from the browser JavaScript API, however a string of code cannot be passed to be executed.

Why does setTimeout 0 not always run immediately?

This is because even though setTimeout was called with a delay of zero, it's placed on a queue and scheduled to run at the next opportunity; not immediately. Currently-executing code must complete before functions on the queue are executed, thus the resulting execution order may not be as expected.

What happens when setTimeout is 0?

setTimeout(fn, 0) !== 0ms. When working with timers, setting a delay of 0, does not equate to 0ms or “instant” execution. This longer than expected delay may occur because the OS/browser/system is busy with other tasks or because the timer has been throttled.


2 Answers

From the node.js api docs for setTimeout(cb, ms) (emphasis mine):

It is important to note that your callback will probably not be called in exactly delay milliseconds - Node.js makes no guarantees about the exact timing of when the callback will fire, nor of the ordering things will fire in. The callback will be called as close as possible to the time specified.

I suppose that "as close as possible" means something different to the implementation team than to you.

[Edit] Incidentally, it appears that the setTimeout() function isn't mandated by any specification (although apparently part of the HTML5 draft). Moreover, there appears to be a 4-10ms de-facto minimum level of granularity, so this appears to be "just how it is".

The great thing about open source software is that you can contribute a patch to include a higher resolution per your needs!

like image 175
maerics Avatar answered Oct 21 '22 19:10

maerics


For completeness I would like to point out to the nodeJS implementation:

https://github.com/nodejs/node-v0.x-archive/blob/master/lib/timers.js#L214

Which is:

// Timeout values > TIMEOUT_MAX are set to 1.
var TIMEOUT_MAX = 2147483647; // 2^31-1
...
exports.setTimeout = function(callback, after) {
    var timer;

    after *= 1; // coalesce to number or NaN

    if (!(after >= 1 && after <= TIMEOUT_MAX)) {
        after = 1; // schedule on next tick, follows browser behaviour
    }

    timer = new Timeout(after);
    ...
}

Remember this statement:

IDLE TIMEOUTS

Because often many sockets will have the same idle timeout we will not use one timeout watcher per item. It is too much overhead.
Instead we'll use a single watcher for all sockets with the same timeout value and a linked list.

This technique is described in the libev manual: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts

And we pass the same timeout value (1) here.

The implementation for Timer is here:
https://github.com/nodejs/node-v0.x-archive/blob/master/src/timer_wrap.cc

like image 23
Stefan Rein Avatar answered Oct 21 '22 19:10

Stefan Rein