Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

setTimeout(fn, 0) running before setImmediate #6034

Closed
kesla opened this issue Aug 10, 2013 · 18 comments
Closed

setTimeout(fn, 0) running before setImmediate #6034

kesla opened this issue Aug 10, 2013 · 18 comments

Comments

@kesla
Copy link

kesla commented Aug 10, 2013

Running

setTimeout(function() {
  console.log('setTimeout')
}, 0)

setImmediate(function() {
  console.log('setImmediate')
})

will result in

setTimeout
setImmediate

even though the current docs state that "To schedule the "immediate" execution of callback after I/O events callbacks and before setTimeout and setInterval." (http://nodejs.org/docs/latest/api/all.html#all_setimmediate_callback_arg)

Are the docs unclear or is this a real bug?

cc @trevnorris

@kesla
Copy link
Author

kesla commented Aug 10, 2013

Also, related to #5943 and #5950

@bnoordhuis
Copy link
Member

the current docs state that "To schedule the "immediate" execution of callback after I/O events callbacks and before setTimeout and setInterval."

Documentation bug (but a small one).

The event loop cycle is timers -> I/O -> immediates, rinse and repeat. The documentation is correct but incomplete: it doesn't mention that when you haven't entered the event loop yet (as is the case in your example), then timers come first - but only on the first tick. (In master. To complicate matters, things work slightly less deterministic in v0.10.)

If you wrap your example in another timer, it always prints setImmediate followed by setTimeout.

setTimeout(function() {
  setTimeout(function() {
    console.log('setTimeout')
  }, 0);
  setImmediate(function() {
    console.log('setImmediate')
  });
}, 10);

@willkan
Copy link

willkan commented Dec 3, 2013

@bnoordhuis Would you explain the event loop cycle more explicitly?

When have entered event loop, what's the priority of timers, I/O and immediates?

In your code above

setTimeout(function() {
  setTimeout(function() {
    console.log('setTimeout')
  }, 0);
  setImmediate(function() {
    console.log('setImmediate')
  });
}, 10);

Follow the cycle you mentioned above:

timers -> I/O -> immediates

The code will be executed like this:

  • run the code :set the wrapper timer
  • after 10ms, enter the first event loop
  • run the callback of the wrapper timer
    • set the inner timer
    • set the immediate
  • enter next event loop
  • run the callback of the inner timer
  • run the callback of the immediate

Result should be

setTimeout
setImmediate

But actually, result is

setImmediate
setTimeout

Would you tell me where is the place I misunderstanding? Thanks.

@rlidwka
Copy link

rlidwka commented Dec 6, 2013

node.js loop

Disclaimer: image was created at least a year ago, and I have no idea who the original author is

@yorkie
Copy link

yorkie commented Dec 6, 2013

+1 good graph, thx @rlidwka

@Mithgol
Copy link

Mithgol commented Dec 6, 2013

If on the above graph setTimeout runs on step 2 and setImmediate runs on step 6, then how can the latter precede the former as @willkan said?

@tjfontaine
Copy link

timers are based on a time in the future, even if it's 0, while check immediate is always on the next turn of the loop. So it's possible that the delay of the event loop is low enough for the timer to fire after the immediate.

You shouldn't necessarily be concerned about the order of operations in this regard though in my estimation.

@trevnorris
Copy link

One correction. process.nextTick() can and will only run after another callback has run. So where it has process.nextTick() on 3, that should be attached to the setTimeout()/setInterval() above.

@tjfontaine
Copy link

re #5950

@willkan
Copy link

willkan commented Dec 7, 2013

So it's possible that the delay of the event loop is low enough for the timer to fire after the immediate.

I think this is the best answer to my question. Thank you, @tjfontaine
And thanks to @rlidwka 's graph, good explanation!

@rlidwka
Copy link

rlidwka commented Dec 7, 2013

And thanks to @rlidwka 's graph, good explanation!

You're welcome. You might also want to thank a real author of that image, though I have no idea who he is. :-)

@mrhooray
Copy link

Thanks @rlidwka, but the image is broken. Could you still find the graph?

@rlidwka
Copy link

rlidwka commented Feb 20, 2014

@mrhooray
Copy link

Thanks, @rlidwka!

@rogierschouten
Copy link

Having different behavior on first tick is a real problem. In a-synchronous systems, a frequently occurring design pattern is what I call "sentinel event". When you cancel an a-synchronous operation, there is a race condition between the operation completing and the operation being cancelled. To avoid this, you introduce an extra callback to signal completion of the cancel() method. This callback should always arrive AFTER a possible completion callback, so that the caller knows when not to expect callbacks anymore.

How can this be realized in Node? Is it guaranteed that using setTimeout(cancelCallback, 0) always arrives after any previous completion callbacks scheduled with nextTick, setImmediate, or I/O result?

@trevnorris
Copy link

@rogierschouten The fact you bring up nextTick() in your set of possibilities shows a fundamental lack of understanding in how the internals work. nextTick(), TBH, is poorly named, but what it does is queue up callbacks to be run at the end of the current synchronously running script. So it'll execute more code before the event loop can proceed. IOW, nextTick() will always run before any other truly asynchronous operation that depends on the event loop proceeding.

We don't guarantee that any differing set of asynchronous functions run before any other. The only guarantee is that in the case:

setTimeout(function A() { }, 10);
setTimeout(function B() { }, 20);

Function A() will run before B(). That's because they depend on the same internal queue. But anything through setImmediate() and setInterval() each have their own queue, and are not dependent on one another in terms of timing.

@kesla It's incorrect to say that the setTimeout() will always run before the setImmediate(). Run that many times and you'll see that the order changes. TBH, I have no idea why. That's an implementation detail that might be worth looking into, but not on my priority list.

@jasnell
Copy link
Member

jasnell commented May 28, 2015

@trevnorris ... is this something we still need to keep open?

@trevnorris
Copy link

nope

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests