-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
Clarify when nextTickQueue is processed #1804
Conversation
/cc:@nodejs/documentation. Please have a careful check about this detailled describtions :) |
I think this is a better description but I'm no event loop expert. pinging people who can shed some light @addaleax @mcollina @Fishrock123 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
I think you may have found a problem, the current text may not be correct for all types of operations, but I am not sure the replacement is correct either. Perhaps not all operations are the same? The original text used "operations" because not all phases contain events. Operations is a more generic term for what phases do:
"events" in the sense of the event loop are read from the kernel only in the Poll phase, so the replacement shouldn't replace operations with events, they aren't the same. I noticed what you noticed with your example code, too, when I wrote some exploratory code, but I think its particular to "immediates". In that case, it appears the entire immediate queue is executed sequentially, and then any ticks that were queued by the immediates are executed sequentially. In contrast, when an I/O event causes a callback to be executed, and that callback calls |
An I/O event triggers some JavaScript to be executed. At the end of that execution, the functions in the |
@mcollina That is my understanding as well, so the change proposed in this PR is incorrect. It says that the nextTick queue won't be executed until the end of the entire phase. @OmarDarwish I think that executing the entire queue of Immediates is a single "operation", from the point of view of clearing the |
I'm up for any change in the documentation that makes this particular (edge?) case well known. @sam-github what do you think is a better way to make this easier to understand for the reader? |
I think we should clarify the phases, and reflect the fact that there is a single transition between C to JS for multiple immediates and timers. In fact, we are deduping them as a form of optimization and this is a side effect of that. |
Took a stab at defining an operation, and giving an example of deduplication. |
@@ -190,8 +190,6 @@ _whose time thresholds have been reached_. If one or more timers are | |||
ready, the event loop will wrap back to the **timers** phase to execute | |||
those timers' callbacks. | |||
|
|||
### check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you remove this by accident?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, lemme fix real quick 😅
@@ -20,7 +20,7 @@ this in further detail later in this topic. | |||
## Event Loop Explained | |||
|
|||
When Node.js starts, it initializes the event loop, processes the | |||
provided input script (or drops into the [REPL][], which is not covered in | |||
provided input script (or drops into the [REPL](https://nodejs.org/api/repl.html), which is not covered in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This line exceeds 80 characters limit now.
- It seems you can just fix the bottom reference if you prefer this URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nits.
@@ -303,6 +306,56 @@ situations because **it allows you to "starve" your I/O by making | |||
recursive `process.nextTick()` calls**, which prevents the event loop | |||
from reaching the **poll** phase. | |||
|
|||
#### Deduplication | |||
|
|||
For the `timers` and `check` phases, the there is a *single transition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the there -> there
|
||
For the `timers` and `check` phases, the there is a *single transition | ||
between C to JS for multiple immediates and timers*. This deduplication | ||
is a form of optimzation, which may produce some unexpected side effects. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optimzation -> optimization
const foo = [1, 2]; | ||
const bar = ['a', 'b']; | ||
|
||
foo.forEach( num => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneeded space before num
.
|
||
At this point, all previously added `setImmediate()` events have been processed. | ||
The `nextTickQueue` is now checked, and events are processed in FIFO order. When | ||
these `nextTickQueue` is emptied, the event loop is considers all operations to have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these -> this
is considers -> considers
to have completed -> to have been completed?
@vsemozhetbyt Thanks for double checking. I forgot to spell check before pushing. Fixing now! |
@sam-github @mcollina What do you think? If I understand the contributing policy, if/when you two approve we should be good to go to merge? |
I think responsibility for this repo is to @nodejs/website. |
I just realized this doc doesn't discuss promises, which I believe have their own queue and processing timings which make them unique. async function doWork(char) {
console.log('promise', char);
}
// dedup.js
const foo = [1, 2];
const bar = ['a', 'b'];
foo.forEach(num => {
setImmediate(() => {
console.log('setImmediate', num);
bar.forEach(async (char) => {
process.nextTick(() => {
console.log('process.nextTick', char);
});
await doWork(char);
});
});
}); setImmediate 1
promise a
promise b
setImmediate 2
promise a
promise b
process.nextTick a
process.nextTick b
process.nextTick a
process.nextTick b Maybe this PR isn't the right place to address this... |
FWIW Here is an academic paper on this by @matthewloring and others. |
@mcollina, thanks for the ping. This does fit in well with our redesign / getting started guide work, but I think its okay for the content to live in here for now. We're hoping to keep all getting started guides alongside the source code after we've figured out the docs site content ingestion pipeline, so it'll end up back here anyway. The new content here will be helpful in the meantime. We'll definitely use this doc as a starting point for for the event loop page on the new site (likely take it wholesale) awesome job @OmarDarwish! |
@dmcghan Your example isn't quite accurate. Promises would normally only resolve after the nextTicks. In your case, you have
Once #22842 lands though, it will be more like the browser:
|
completes, regardless of the current phase of the event loop. | ||
the `nextTickQueue` will be processed after the current operation is | ||
completed, regardless of the current phase of the event loop. Here, | ||
an **operation** is defined as a transition from the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Italics here, not bold.
#### Deduplication | ||
|
||
For the `timers` and `check` phases, there is a *single transition | ||
between C to JS for multiple immediates and timers*. This deduplication |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: No italics here. We overuse italics and bold for emphasis. Just don't do it. 😄
So in theory, the correct content of these guides is dependent on the version of Node.js... |
(If this is an improvement over existing text, I'm totally OK with it landing with or without my nits addressed.) |
@apapirovski Interesting. Looking back, I'm not sure I meant to add the Maybe this is a bit simpler: const foo = [1, 2];
const bar = ['a', 'b'];
foo.forEach(num => {
setImmediate(() => {
console.log('setImmediate', num);
bar.forEach(async (char) => {
process.nextTick(() => {
console.log('process.nextTick', char);
});
new Promise((resolve, reject) => {
console.log('promise', char);
});
});
});
}); You can reverse the order of the nextTick and Promise calls and the result is the same (expected): setImmediate 1
promise a
promise b
setImmediate 2
promise a
promise b
process.nextTick a
process.nextTick b
process.nextTick a
process.nextTick b This output surprises me. I'd have expected the output you showed:
I'm missing something. Does no While I'd like to get a better understanding of this behavior, I was really just trying to point out that this level of detail isn't in the doc. Do you have a link to 22842? |
@@ -303,6 +306,56 @@ situations because **it allows you to "starve" your I/O by making | |||
recursive `process.nextTick()` calls**, which prevents the event loop | |||
from reaching the **poll** phase. | |||
|
|||
#### Deduplication |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if this is the correct place. The next section answers why the behaviour mentioned in the line above is allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, you're right. Let me fix this.
@OmarDarwish:There're too many changes here, so can you merge them by using "rebase..." in your local PC and make a push submit? Not sure whether this post can be merged? I think it's been a long time.... |
Define an operation. Give example of dedup. Remove empty section Fix trigger happy deletions Remove modififed REPL link Spellcheck Deduplication section Address Nits * Remove italics. * Change italics to bold Move Deduplication sectino down Update locale/en/docs/guides/event-loop-timers-and-nexttick.md Co-Authored-By: OmarDarwish <om.drwsh@gmail.com> Update locale/en/docs/guides/event-loop-timers-and-nexttick.md Co-Authored-By: OmarDarwish <om.drwsh@gmail.com>
@Maledong I've rebased and squashed |
@OmarDarwish:Thanks! @watson, @sam-github , @thefourtheye , @Trott :Any other ideas or suggestions? Can this be merged? |
process.nextTick b | ||
process.nextTick a | ||
process.nextTick b | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK, did the output incorrect ?
The output is always as below while my Node.js version is v11.4.0
.
setImmediate 1
process.nextTick a
process.nextTick b
setImmediate 2
process.nextTick a
process.nextTick b
So it's indeed as @Trott said:
So in theory, the correct content of these guides is dependent on the version of Node.js...
Maybe we also need to explain this there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. Looks like something changed for node 11:
bash-3.2$ pbpaste
const foo = [1, 2];
const bar = ['a', 'b'];
foo.forEach(num => {
setImmediate(() => {
console.log('setImmediate', num);
bar.forEach(char => {
process.nextTick(() => {
console.log('process.nextTick', char);
});
});
});
});
bash-3.2$ nvm use 10.15.0
Now using node v10.15.0 (npm v6.4.1)
bash-3.2$ pbpaste | node
setImmediate 1
setImmediate 2
process.nextTick a
process.nextTick b
process.nextTick a
process.nextTick b
bash-3.2$ nvm use 11.0.0
Now using node v11.0.0 (npm v6.4.1)
bash-3.2$ pbpaste | node
setImmediate 1
process.nextTick a
process.nextTick b
setImmediate 2
process.nextTick a
process.nextTick b
bash-3.2$ nvm use 11.6.0
Now using node v11.6.0 (npm v6.5.0-next.0)
bash-3.2$ pbpaste | node
setImmediate 1
process.nextTick a
process.nextTick b
setImmediate 2
process.nextTick a
process.nextTick b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like guides is not segregated by version. What would be the best way to indicate that this behavior is only applicable for Node 10 and below?
Also, what changed in node 11?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what changed nodejs/node#22842
Can this be merged? It's been for ages :) |
I think all changes have been addressed I'm +1 on merging this. |
It was a little unclear for me when the
nextTickQueue
is processed (see this StackOverflow question). After some experimentation and more googling, I now understand the mechanics of this queue.I'm hoping to make this a little more clear for the next reader!