You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Revisiting this article, I've found out that an example which illustrates some known bug (?) in microtask queue execution behaves differently because of times of script call.
'use strict';console.log('script start')constinterval=setInterval(()=>{console.log('setInterval')},0)setTimeout(()=>{console.log('setTimeout 1')Promise.resolve().then(()=>{console.log('promise 3')}).then(()=>{console.log('promise 4')}).then(()=>{setTimeout(()=>{console.log('setTimeout 2')//< place of random microtask queue intrusionPromise.resolve().then(()=>{console.log('promise 5')}).then(()=>{console.log('promise 6')}).then(()=>{clearInterval(interval)})},0)})},0)Promise.resolve().then(()=>{console.log('promise 1')}).then(()=>{console.log('promise 2')})
Every first call in a new cmd.exe terminal produces the mentioned error. Every next call has proper execution order:
changed the title [-]strange dependence between event loop and script times of script call[/-][+]strange dependence between event loop and times of script call[/+]on Aug 30, 2017
On my box it is random, with each run the output will either have or have not this extra setInterval.
The docs for setTimeout and setInterval say When delay is larger than 2147483647 or less than 1, the delay will be set to 1, so I guess that this is some kind of a race?
The article says: According to the WHATVG specification, exactly one (macro)task should get processed from the macrotask queue in one cycle of the event loop
while Nodes setImmediate documentation says: The entire callback queue is processed every event loop iteration.
So I guess Node does not follow WHATVG exactly. More details are here.
I think that the thing here is that in the article uses setTimeout(..., 0). Node makes this into setTimeout(..., 1). This 1 is a minimum delay, so it probably gets scheduled slightly differently each time.
Just pointing out that the whole micro/macrotask semantics aren't really specified anywhere - and we're free to schedule promises (as a platform as we choose).
We currently schedule them with microtask semantics (again, this is not required by the specification but is more correct since it does not defer things by I/O).
I'd like to point out that the order of callbacks will likely change in the future as a part of nodejs/promises#31 since we currently needlessly defer a lot of actions to the nextTick where we don't have to which I hope V8 will fix.
Have you tested what happens when you replace all the Promise.resolve().then(() => ... calls with nextTick calls?
@benjamingr If I get this right, this is the equivalent:
'use strict';console.log('script start');constinterval=setInterval(()=>{console.log('setInterval');},0);setTimeout(()=>{console.log('setTimeout 1');process.nextTick(()=>{console.log('nextTick 3');});process.nextTick(()=>{console.log('nextTick 4');});process.nextTick(()=>{setTimeout(()=>{console.log('setTimeout 2');//< place of random microtask queue intrusionprocess.nextTick(()=>{console.log('nextTick 5');});process.nextTick(()=>{console.log('nextTick 6');});process.nextTick(()=>{clearInterval(interval);});},0);});},0);process.nextTick(()=>{console.log('nextTick 1');});process.nextTick(()=>{console.log('nextTick 2');});
It is more random for me (even the first run in the new terminal has random output), but the extra setInterval emerges inside microtask queue more seldom:
For example, Bluebird an Q which are the two most popular promise libraries schedule promises with macrotask semantics (with setImmediate) and that has never been a problem although we considered changing it many times. The issue just never rose.
@vkurchatkin right, but Bluebird schedules with setImmediate and macrotask semantics explicitly. (It runs all scheduled tasks together though, rather than schedule them individually)
@vkurchatkin I don't think it is, but if you have a compelling argument on why nextTick semantics is inherently better I'm all ears. Node that bluebird typically doesn't schedule at all - only when the promise itself resolved synchronously.
There are plenty of reasons to not pick bluebird and hopefully we will reach performance parity with native promises but scheduling on macrotick semantics worked so well (well enough) that the two most popular libraries with over 25M monthly downloads on NPM do it and (almost) nobody noticed.
This seems not OK: in the event loop go-around 3 we have microtask queue from the event loop go-around 2.
If the answer is not true, what would be the separation?
We have one missing macrotask in the event loop go-around 1 (and reverted order of handler execution in event loop go-around 2? — or setTimeout/setInterval order in the next setInterval runs is random by design? ).
I am trying to visually separate event loop go-around with setImmediate() calls (I am not sure if this is a right approach though). So this is a simplified version of the above-used cases:
Well, I've read some articles (and watched some videos) about event loop with all these schemes of event loop phases and careful statements about execution in order of queueing — and still did not know that we had so unpredictable distribution of queued tasks between event loop go-arounds(
If this is true, it seems there is no unexpectedness in the original article example output or next outputs in this issue. So I can close this issue for now. Feel free to reopen if needed or to add any useful information.
Activity
[-]strange dependence between event loop and script times of script call[/-][+]strange dependence between event loop and times of script call[/+]vsemozhetbyt commentedon Aug 30, 2017
The same is valid for v6.11.2, the last nightly and V8 canary.
bzoz commentedon Aug 30, 2017
On my box it is random, with each run the output will either have or have not this extra
setInterval.The docs for
setTimeoutandsetIntervalsayWhen delay is larger than 2147483647 or less than 1, the delay will be set to 1, so I guess that this is some kind of a race?vsemozhetbyt commentedon Aug 30, 2017
@bzoz I've checked some more times: the first time is always with extra
setInterval, in the next ones — ~1 of 10 has extrasetInterval.vsemozhetbyt commentedon Aug 30, 2017
Strangely, in git bash for Windows, the first time is also random, in the next ones statistics is reverted: ~1 of 10 has not extra
setInterval.bzoz commentedon Aug 30, 2017
On my box it is totally random, no matter if on
cmd,bashor anything.mcollina commentedon Aug 30, 2017
This is not a problem, but it depends on when
setTimeout(func, 0)placesfunc. This is explained in https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick.bzoz commentedon Aug 30, 2017
The article says:
According to the WHATVG specification, exactly one (macro)task should get processed from the macrotask queue in one cycle of the event loopwhile Nodes
setImmediatedocumentation says:The entire callback queue is processed every event loop iteration.So I guess Node does not follow WHATVG exactly. More details are here.
I think that the thing here is that in the article uses
setTimeout(..., 0). Node makes this intosetTimeout(..., 1). This 1 is a minimum delay, so it probably gets scheduled slightly differently each time.vsemozhetbyt commentedon Aug 30, 2017
This variant has the same behavior:
benjamingr commentedon Aug 30, 2017
Very nice find!
Just pointing out that the whole micro/macrotask semantics aren't really specified anywhere - and we're free to schedule promises (as a platform as we choose).
We currently schedule them with microtask semantics (again, this is not required by the specification but is more correct since it does not defer things by I/O).
I'd like to point out that the order of callbacks will likely change in the future as a part of nodejs/promises#31 since we currently needlessly defer a lot of actions to the
nextTickwhere we don't have to which I hope V8 will fix.Have you tested what happens when you replace all the
Promise.resolve().then(() => ...calls withnextTickcalls?vsemozhetbyt commentedon Aug 30, 2017
@benjamingr If I get this right, this is the equivalent:
It is more random for me (even the first run in the new terminal has random output), but the extra
setIntervalemerges inside microtask queue more seldom:benjamingr commentedon Aug 30, 2017
@vsemozhetbyt good, that looks a lot more manageable than if the problem was in the promise glue code but not in the nextTick queue.
Removing the libuv and promises labels and adding the timers label. Also pinging @mscdex about timers :)
17 remaining items
benjamingr commentedon Aug 30, 2017
For example, Bluebird an Q which are the two most popular promise libraries schedule promises with macrotask semantics (with setImmediate) and that has never been a problem although we considered changing it many times. The issue just never rose.
vkurchatkin commentedon Aug 30, 2017
@benjamingr the equivalent of
nextTickis microtask queue (job queue).benjamingr commentedon Aug 30, 2017
@vkurchatkin right, but Bluebird schedules with
setImmediateand macrotask semantics explicitly. (It runs all scheduled tasks together though, rather than schedule them individually)vkurchatkin commentedon Aug 30, 2017
@benjamingr well, that's one reason to avoid Bluebird.
benjamingr commentedon Aug 30, 2017
@vkurchatkin I don't think it is, but if you have a compelling argument on why nextTick semantics is inherently better I'm all ears. Node that bluebird typically doesn't schedule at all - only when the promise itself resolved synchronously.
There are plenty of reasons to not pick bluebird and hopefully we will reach performance parity with native promises but scheduling on macrotick semantics worked so well (well enough) that the two most popular libraries with over 25M monthly downloads on NPM do it and (almost) nobody noticed.
vsemozhetbyt commentedon Aug 30, 2017
Is this answer, referenced in some place, applicable for Node.js?
In both cases, this output fragment still seems strange:
How can we parse it if the answer is true?
This seems not OK: in the event loop go-around 3 we have microtask queue from the event loop go-around 2.
If the answer is not true, what would be the separation?
We still have microtask queue from the previous loop go-around.
We have one missing macrotask in the event loop go-around 1 (and reverted order of handler execution in event loop go-around 2? — or setTimeout/setInterval order in the next setInterval runs is random by design? ).
We have two identical macrotasks in one event loop go-around
benjamingr commentedon Aug 30, 2017
We should probably document that the ordering of asynchronous actions is not guaranteed by Node.js.
vsemozhetbyt commentedon Aug 31, 2017
I am trying to visually separate event loop go-around with
setImmediate()calls (I am not sure if this is a right approach though). So this is a simplified version of the above-used cases:It randomly outputs these 2 variants:
but this is applicable:
vkurchatkin commentedon Aug 31, 2017
@vsemozhetbyt well, node doesn't have macrotasks, so it's definitely not applicable.
benjamingr commentedon Aug 31, 2017
Is this something you agree with in general - or do you think the current way is fine?
(I'm +0, I think Node.js has traditionally been doing a good job in communicating that this is unspecified)
vsemozhetbyt commentedon Aug 31, 2017
Well, I've read some articles (and watched some videos) about event loop with all these schemes of event loop phases and careful statements about execution in order of queueing — and still did not know that we had so unpredictable distribution of queued tasks between event loop go-arounds(
vkurchatkin commentedon Aug 31, 2017
Well, timers are uninvolved, so it shouldn't be surprising.
vsemozhetbyt commentedon Sep 4, 2017
Closure.
With
asynk_hooks, I can see that there is no guarantee of timers distribution between loop runs.With this code:
I get at least these variants (brackets mean
TIMERWRAPgroup, semicolons separate loop phases):1 (
setInterval) + (setTimeout);setImmediate; (setInterval+setTimeout);setImmediateOutput:
2.1 (
setInterval+setTimeout) + (setInterval+setTimeout);setImmediate+setImmediateOutput:
2.2 (
setInterval) + (setTimeout+setInterval) + (setTimeout);setImmediate+setImmediateOutput:
If this is true, it seems there is no unexpectedness in the original article example output or next outputs in this issue. So I can close this issue for now. Feel free to reopen if needed or to add any useful information.
[-]setInterval randomly intrudes in microtask queue[/-][+]timers distribution predictability[/+]