-
Notifications
You must be signed in to change notification settings - Fork 285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
non-deterministic order of execution of setTimeout vs setImmediate #392
Comments
I think that’s exactly it. You can check that this makes sense by increasing the timeout to, say, 50, and you can see a clear difference in behaviour when adding |
As per the documentation, There is a bug in nodejs/node#7145 though that means that this doesn't always work. The upshot though is that you should always expect |
From documentation:
It says that immediates are executed after events and before timers, it describes the order of execution in the event loop, and since it is a loop, another way of describing it:
is also true. It depends from which point we look at the event loop. how
order in the source code:
Important thing here is that timers phase comes directly after main code has been executed. It is the first phase of the event loop. If there are expired timers at this point, they will be executed before any nodejs/node#7145 is not a bug. while there are any expired callbacks then run the callbacks; otherwise proceed to the next phase. Since you're adding another timer inside of a Timers phase will only end if there are no more expired timers. If new timers are being added in the timer callbacks, it is possible that timer phase will never end. Example: const timer = () => {
setTimeout(timer, 1)
setImmediate(() => console.log('immediate fired')) // this will never be logged
const date = new Date()
while (new Date() - date < 2) {}
}
timer() |
Hi @marzelin, Thanks for your long explanation. The code that I put up is what you should be looking at. Let me take you through it. So, there is two events which are being handled -
The important bit though is Then it fires off 2
So, all 3 events are queued in the same tick so if keeping to the spec the 2 BTW, in your example, you have specified that Regards, |
When
it is guaranteed that For example, when calling const readdir = require('fs').readdir
let immediateFired;
let numberOfIterations = 0;
// Handle any errors or rejections
process.on('uncaughtException', (err) => {
console.error(`uncaughtException handled on iteration ${numberOfIterations}`)
console.error(`immediateFired: ${immediateFired}`)
process.exit(1);
});
console.log(`Running test at ${new Date().toISOString()} on`, process.release, process.arch);
process.on('beforeExit', function() {
immediateFired = 0;
numberOfIterations++;
////////////////////////////////////////////////////////////////////////////////
// main() // main function is called from 'close callbacks' phase (expired timers are executed before immediates)
// setTimeout(main, 1) // main function is called from 'due timers' phase (expired timers are executed before immediates)
// setImmediate(main) // main function is called from 'check handles' phase (expired timers are executed before immediates)
readdir(__dirname, main); // main function is called from 'Poll for I/O' phase (immediates are executed before expired timers)
///////////////////////////////////////////////////////////////////////////////
});
function main() {
setImmediate(() => {
immediateFired++;
// console.log('setImmediate 1 fired');
});
setTimeout(() => {
if (immediateFired < 2) {
throw new Error('setTimeout was fired before set immediate');
}
// console.log('setTimeout 1');
}, 1);
setImmediate(() => {
immediateFired++;
// console.log('setImmediate 2 fired');
});
} |
As Node.js doc says:
Why ?. Every event in node.js is driven by
So As explained by the node.js Doc, we can match each Phase of the event loop in the code. Timer Phase I/O Callback idle/ prepare poll check close callbacks More than phaseBut if we see the code closely before the loop goes to timer phase, it calls
What happens is
This call to
Once this is returned Timer phase is called in the event loop.
The callback in the Timer phase is run if the current time of loop is greater than the timeout. If the preparation before the first loop took more than Hopefully this clarifies the way around the non-deterministic behaviour of |
Great answer. Thanks @ankur-anand |
@marzelin The example did not work as expected and |
@ackvf timers were broken before nodejs/node#3063 landed in v6.3.1 Since then, the behavior of timers was as I presented here. And the example code still works on node versions that were shipped from roughly the second half of 2016 to the end of 2017 i.e. 6.3.1 - 6.12.0 or 8.0.0 - 8.7.0 (you can check it easily with Although this behavior doesn't violate any specification, having timers that can starve the loop is not particularly useful, to say the least. So, since nodejs/node#15072 landed, you can't block the event loop with |
Referring to @marzelin 's comment here - If new (immediately expiring) timers are being added while executing the current setTimeout, how can it be added and executed in the same iteration. From what i understood it will not be picked up until the next iteration of the event loop, - the reason being this below, from libuv design |
But why when setImmediate() is scheduled under Poll phase, its callback is always executed before setTimeout? Does 2nd tick bypass Timer phase? or is setImmediate callback executed directly in 1st tick? fs.readFile('xx', () => {
setTimeout(() => { console.log('timeout'); }, 0);
setImmediate(() => { console.log('Immediate'); });
}) I have read the node blog and everywhere else, still don't find the exact reason. |
The flow of execution in the event loop is strictly specified:
Hence, when we have main script:
with
setTimeout()
callback with the0
delay time (which makes it due initially) should be called beforesetImmediate()
callbacks that are executed in check phase that takes place after timers phase.But it is not so. The order is pretty random.
Is that because
setTimeout()
delay when set to0
is internally converted to1
(which may or may not be due when checked in the timers phase), or is there more factors that contribute to this non-deterministic behavior?The text was updated successfully, but these errors were encountered: