Skip to content
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

Closed

Conversation

apapirovski
Copy link
Member

@apapirovski apapirovski commented Dec 18, 2017

A few changes to nextTick to further simplify the code as much as possible:

  • Do not share unnecessary information about nextTick state between JS & C++, instead only track whether a nextTick 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.
  • Turn nextTickQueue into an Object 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.
  • Other assorted refinements and refactoring.

CI: https://ci.nodejs.org/job/node-test-pull-request/12174/

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)

process, src

@apapirovski apapirovski added lib / src Issues and PRs related to general changes in the lib or src directory. process Issues and PRs related to the process subsystem. labels Dec 18, 2017
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. process Issues and PRs related to the process subsystem. labels Dec 18, 2017
@apapirovski apapirovski force-pushed the patch-next-tick-refactor branch 2 times, most recently from 54e624e to 5ababbd Compare December 18, 2017 14:52
@@ -179,6 +140,8 @@ function setupNextTick() {
triggerAsyncId,
this);
}

nextTickQueue.push(this);
Copy link
Member

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…

Copy link
Member Author

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;
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Member Author

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.)

Copy link
Contributor

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.

Copy link
Member Author

@apapirovski apapirovski Dec 18, 2017

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.

Copy link
Member Author

@apapirovski apapirovski Dec 18, 2017

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. 😆

Copy link
Member Author

@apapirovski apapirovski Dec 18, 2017

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

Copy link
Member

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. :)

@apapirovski
Copy link
Member Author

FWIW, here's the benchmark CI results:

 process/next-tick-breadth-args.js millions=4      0.88 %            5.225539e-01
 process/next-tick-breadth.js millions=4          -0.49 %            4.959895e-01
 process/next-tick-depth-args.js millions=12       5.25 %        *** 4.221255e-18
 process/next-tick-depth.js millions=12            5.55 %        *** 1.263990e-10
 process/next-tick-exec-args.js millions=5         0.97 %            4.162916e-01
 process/next-tick-exec.js millions=5              1.76 %        *** 2.312287e-04

@addaleax addaleax added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 19, 2017
@apapirovski apapirovski force-pushed the patch-next-tick-refactor branch 3 times, most recently from 079dfed to 55b2a46 Compare December 19, 2017 18:57
@apapirovski
Copy link
Member Author

apapirovski commented Dec 19, 2017

var kLength = 1;
const kScheduled = 0;

const nextTickQueue = {
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member

@AndreasMadsen AndreasMadsen Dec 20, 2017

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.

Copy link
Member Author

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.

Copy link
Member

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 :)

Copy link
Member Author

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.
@apapirovski
Copy link
Member Author

@apapirovski
Copy link
Member Author

Landed in 4444b6b

@apapirovski apapirovski deleted the patch-next-tick-refactor branch December 24, 2017 14:14
apapirovski added a commit that referenced this pull request Dec 24, 2017
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 <anna@addaleax.net>
@addaleax addaleax removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 29, 2017
@MylesBorins
Copy link
Contributor

This does not land cleanly on v9.x, could you please backport

@apapirovski
Copy link
Member Author

apapirovski commented Jan 10, 2018

This will land cleanly once #18079 lands.

apapirovski added a commit to apapirovski/node that referenced this pull request Jan 31, 2018
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 <anna@addaleax.net>
apapirovski added a commit to apapirovski/node that referenced this pull request Feb 26, 2018
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 <anna@addaleax.net>
addaleax pushed a commit that referenced this pull request Feb 26, 2018
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 <anna@addaleax.net>
addaleax pushed a commit that referenced this pull request Feb 26, 2018
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 <anna@addaleax.net>
MylesBorins pushed a commit that referenced this pull request Feb 26, 2018
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 <anna@addaleax.net>
@addaleax addaleax mentioned this pull request Feb 27, 2018
@MylesBorins
Copy link
Contributor

Should this be backported to v8.x-staging? If yes please follow the guide and raise a backport PR, if not let me know or add the dont-land-on label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. process Issues and PRs related to the process subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants