1

Objective

Find out the true behavior of setTimeout(fn, X) when X-> 0.

Background

I am developing a tool that makes QPS (Queries Per Second) tests, using setTimeout(). I was recently surprised when I made a test of 1000 QPS that took roughly 5 seconds to execute when it should have taken 1 (not counting any other external factors).

The tests works fine for lower values of QPS, bellow 100 for example.

While investigating, I found some articles explaining that when making setTimeout(fn, X) calls, when X tends to 0, if X is too small, it will get trimmed to a higher value.

An examples of this can be seen in this question from 2011, where the old spec specifies that if setTimeout(fn, X) where X <= 3, X will be automatically set to 4.

This means that the minimum amount of time I can ever hope to wait, between two executions of setTimeout() is 4 milliseconds. If I have setTimeout(fn, 1), it will be converted to setTimeout(fn, 4).

Questions

Every article I see says something different, and in the question I previously posted, different answers say different things as well. The overall all conclusion seems to be "there is no conclusion, as the behavior is highly inconsistent".

Coming back to node.js, and since the questions I pointed is quite old, I would like an update on the following :

  1. One of the answers says that the minimum value of X is 1. Is this still accurate?
  2. How does setTimeout(fn, X) when X -> 0 work?

Conclusion

I would like more information regarding setTimeout() so I can build my code around it. Links to documentation and dates of the articles or answers found will be highly appreciated.


Thanks for the help!

Community
  • 1
  • 1
Flame_Phoenix
  • 16,489
  • 37
  • 131
  • 266
  • 3
    Whether the timeout is actually actively being set to a minimum number or not put aside… there *will* be a minimal delay simply due to the event loop having to cycle once, which in itself has a bit of overhead, and possibly it also having to handle other events in the process. There simply is no guaranteed timing with asynchronous operations beyond *"sometime very soon after `X`"*. – deceze Oct 05 '16 at 09:27
  • What exactly is "QPS"? Javascript timers aren't very accurate, and certainly not down to a millisecond or two. – adeneo Oct 05 '16 at 09:28
  • This may help: http://stackoverflow.com/questions/9288050/why-does-node-js-handle-settimeoutfunc-1-0-incorrectly Node JS is not capable of precise delays. It's always an approximation. Any delay may or may not be met. Values lower as 4 are not met most of the time but values higher than 1000 are also not (Exactly) met. – Sander Oct 05 '16 at 09:28
  • 1
    @Sander unless you're using libraries that specifically implement it. I used [`rpio.usleep()`](https://github.com/jperkin/node-rpio#misc) to accurately implement a realtime(-ish) protocol that required 375μs delays. It _is_ blocking, but blocking the event loop for that amount of time isn't an issue. – robertklep Oct 05 '16 at 09:36
  • For such questions you need look into the docs of the platform that runs you js code, for node.js look here [node.js: Timers](https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg) `[...]When delay is larger than 2147483647 or less than 1, the delay will be set to 1.[...]` – t.niese Oct 05 '16 at 10:54

1 Answers1

2

It is related to the event loop. Imagine a pile of order, that node.js execute one after another.

setTimeout is (dumbed down version) "put this at the end of the pile, and don't execute it before Xmilliseconds".

So while you are certain it will wait at least that time, you will still need to wait for node.js to get that task back at the top of the pile, which can take a bit of time (in the order of milliseconds).

That's also why it is recommended to use process.nextTick, which put the task at the top of the pile, instead of setTimeout(callback, 0).

So in your example, setTimeout(callback, 1) will not be internally transformed to setTimeout(callback, 4), it's just that there is a 3ms overhead before node.js get back to that task once the timer have elapsed. If there is nothing else in the event loop, and your processor is fast, you may be able to cut down another millisecond, but node.js is just not built to handle time sensitive task at that level. That would put it in the realm of Real Time Programming, which is another use altogether.

To put things in perspective, setTimeout is, in a crushing majority of use case, used to handle a handful of seconds, so ~1000ms. Is 2~3ms more really that much of a inconvenience?

process.nextTick will also allow node.js to clean the event queue, and prevent RangeError: Maximum call stack size exceeded exceptions when you chain a lot of asynchronous calls.

DrakaSAN
  • 7,673
  • 7
  • 52
  • 94
  • 1
    You should probably note that `setImmediate` is non-standard and probably never will be standarized. It does work in Node, which is somewhat strange seeing as Google is rather strongly against it, and no browser other than IE supports it. – adeneo Oct 05 '16 at 09:38
  • Good point. I don't find it strange though, `setImmediate` have his use in node.js, but would be nonsense in client-side JS. In node.js, it allow the event queue to be cleared, there is no such need (is there even a event queue?) in client side JS. – DrakaSAN Oct 05 '16 at 09:41
  • Sure, in rare cases where you do recursive calls, and `nextTick` isn't appropriate `setImmediate` is useful. What I meant was that it's a little strange that Node implented a IE-only feature when it's built on V8, Googles engine, and Google seems to be against `setImmediate` all together, but timers are of course not part of the standard and can easily be added etc. – adeneo Oct 05 '16 at 09:49
  • I believe setImmediate does also not guarantee a specific timing. I.e. if many i/o events happen, it may fire some time later than specified. Most applications it may not be a problem but a test of 1000 QPS may still take longer than 1000ms. If this is a problem, you could use process.nextTick. http://stackoverflow.com/questions/15349733/setimmediate-vs-nexttick – Sander Oct 05 '16 at 09:50
  • Ah, I mismatched `process.nextTick` and `setImmediate`. Your question is indeed founded @adeneo – DrakaSAN Oct 05 '16 at 10:09