-
Notifications
You must be signed in to change notification settings - Fork 30k
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
process: refactor nextTick for clarity #17738
Conversation
54e624e
to
5ababbd
Compare
lib/internal/process/next_tick.js
Outdated
@@ -179,6 +140,8 @@ function setupNextTick() { | |||
triggerAsyncId, | |||
this); | |||
} | |||
|
|||
nextTickQueue.push(this); |
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.
Is there a reason to move this into the constructor? I find moving side-effects into constructors to be surprising about 99 % of the time…
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.
Probably not. Was trying to consolidate between the two nextTick
functions but you're right, this likely doesn't belong here.
// large and cause the process to run out of memory. When this value | ||
// is reached the nextTimeQueue array will be shortened (see tickDone | ||
// for details). | ||
const kMaxCallbacksPerLoop = 1e4; |
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.
Doesn't this expose infinite nextTick loop problems? and / or, isn't this a breaking change?
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 became irrelevant when @mscdex turned this into a linked list instead of an Array. I don't really see that it does anything right now other than make sure the indexes don't overflow and since I got rid of length & index, it's no longer necessary.
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.
Also, FWIW, our benchmarks are an indirect test for this and they still pass. (If one were to remove this without removing the index & length values then the integers overflow.)
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 thought this was to prevent infinite nextTick
recursion... I'm not sure how a linkedlist helps that? Maybe I didn't look deep enough? Not sure.
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.
No, it doesn't prevent infinite recursion. Nothing does, our documentation even calls it out as an issue.
When an Array used to be used for nextTick
, we wouldn't clear it until 1e4 runs so it could grow to near Infinite size without this condition to remove stale tick objects.
Edit: This was confusingly worded. What I mean is: before this 1e4 limit was introduced, we never cleared the Array until the whole outer loop completed but this meant that _tickCallback
could run long enough until the nextTickQueue
array was so big that it hogged all available memory. The 1e4 limit was then introduced to occasionally empty the nextTickQueue
so the process wouldn't run out of memory.
With a linked list this is no longer necessary as on each tock
it adjusts the linked list and the GC can do its job whenever it wants/needs to. The only reason that code then remained was for the cases where an integer overflow could occur due to reaching too large of an index & length.
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.
Note: The next tick queue is completely drained on each pass of the event loop before additional I/O is processed. As a result, recursively setting nextTick callbacks will block any I/O from happening, just like a while(true); loop.
https://nodejs.org/dist/latest-v9.x/docs/api/process.html#process_process_nexttick_callback_args
Just try const set = () => process.nextTick(set); set();
in repl
and be prepared to force quit it. 😆
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.
@Fishrock123 here's the commit that introduced this code apapirovski@5757642
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.
@apapirovski That was a very helpful explanation… on some level it’s sad to understand code only once it’s going away, but I always wondered about this. :)
FWIW, here's the benchmark CI results:
|
079dfed
to
55b2a46
Compare
var kLength = 1; | ||
const kScheduled = 0; | ||
|
||
const nextTickQueue = { |
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'm curious, why make this an object literal instead of the class that existed before? I would think V8 still optimizes classes (using hidden classes) better than object literals.
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, It does the same thing with both in terms of hidden classes. (That's part of the reason Object.create(null)
is used in a lot of our code because it prevents creating a hidden class and just skips straight to dictionary object mode.)
The reasoning for the change is mostly that it's straight-up confusing the first time one looks at this code, NextTickQueue
is declared outside of setup
and it appears that multiple queues might be supported until one starts reading into the source.
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.
Also, the tickInfo[kScheduled]
flag is flipped inside push
and shift
now, so declaring it outside of setup
is impossible.
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 see. Properly we could merge the code for the timers
linked list and the nextTick
linked list, but this is fine for now.
PS: new class NextTickQueue {}
is a valid syntax, although I find it equally odd.
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.
Ideally, we wouldn't. nextTick
has a special version that's optimized for its own use because it only needs a singly linked list, whereas timers definitely need doubly linked.
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.
We have very different concepts of what is ideal :)
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.
Using doubly linked list, despite it being completely unnecessary for nextTick, would come with a significant performance regression and memory consumption increase...
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring.
55b2a46
to
1d84f1d
Compare
Post-rebase CI: https://ci.nodejs.org/job/node-test-pull-request/12285/ |
Landed in 4444b6b |
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. PR-URL: #17738 Reviewed-By: Anna Henningsen <[email protected]>
This does not land cleanly on v9.x, could you please backport |
This will land cleanly once #18079 lands. |
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. PR-URL: nodejs#17738 Reviewed-By: Anna Henningsen <[email protected]>
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. PR-URL: nodejs#17738 Reviewed-By: Anna Henningsen <[email protected]>
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. Backport-PR-URL: #19006 PR-URL: #17738 Reviewed-By: Anna Henningsen <[email protected]>
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. Backport-PR-URL: #19006 PR-URL: #17738 Reviewed-By: Anna Henningsen <[email protected]>
Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick is scheduled or not. Turn nextTickQueue into an Object instead of a class since multiple instances are never created. Other assorted refinements and refactoring. Backport-PR-URL: #19006 PR-URL: #17738 Reviewed-By: Anna Henningsen <[email protected]>
Should this be backported to |
A few changes to
nextTick
to further simplify the code as much as possible:nextTick
state between JS & C++, instead only track whether anextTick
is scheduled or not. The current implementation was left over from when this information was actually necessary to share between JS & C++ but that is no longer necessary.nextTickQueue
into anObject
instead of a class since multiple instances are never created. This was something that tripped me up the first time I started reading this code. I'll readily admit it's not a major change but it makes it obvious that there's only ever one queue.CI: https://ci.nodejs.org/job/node-test-pull-request/12174/
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
process, src