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

promises: refactor rejection handling #18207

Closed

Conversation

apapirovski
Copy link
Member

Remove the unnecessary microTasksTickObject for scheduling microtasks and instead use TickInfo to keep track of whether promise rejections exist that need to be emitted. Consequently allow the microtasks to execute on average fewer times, in more predictable manner than previously.

Simplify unhandled & handled rejection tracking to do more in C++ to avoid needing to expose additional info in JS. Unite emitting unhandledRejection and rejectionHandled into a single function: emitPromiseRejectionWarnings, which runs after all nextTicks have executed.

When new unhandledRejections are emitted within an unhandledRejection handler, allow the event loop to proceed first instead. This means that if the end-user code handles all promise rejections on nextTick, rejections within unhandledRejection now won't spiral into an infinite loop.

On the whole, this should hopefully make reasoning about nextTick, promises and promise rejections a whole lot simpler.

Fixes: #17913

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

process, promises, src

@apapirovski apapirovski added c++ Issues and PRs that require attention from people who are familiar with C++. process Issues and PRs related to the process subsystem. lib / src Issues and PRs related to general changes in the lib or src directory. promises Issues and PRs related to ECMAScript promises. labels Jan 17, 2018
@nodejs-github-bot nodejs-github-bot added 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. labels Jan 17, 2018
@apapirovski
Copy link
Member Author

apapirovski commented Jan 17, 2018

@apapirovski
Copy link
Member Author

CitGM failures unrelated to this PR but clearly something landed in the last 24 hours that completely destroyed CitGM.

@benjamingr benjamingr self-assigned this Jan 17, 2018
@benjamingr
Copy link
Member

benjamingr commented Jan 17, 2018

I'll need a few days to digest this and run through the edge cases we had when we specified the hooks.

Pinging (no pressure to participate!) relevant parties @petkaantonov @addaleax @domenic

@apapirovski
Copy link
Member Author

apapirovski commented Jan 17, 2018

Just to distill what's going on in the current loop, here's an outline:

  1. Check if any next ticks exist (C++)
    a. If none exist, run Microtasks
    b. If any ticks exist or unhandled/handled promise rejections were added, go to 2; otherwise exit
  2. Execute all currently scheduled next ticks (if any)
  3. Execute microtasks
  4. Check if any next ticks exist, if they do go to 2.
  5. Emit async handled rejections
  6. Emit all currently existing unhandled promise rejections
    a. if any unhandledRejection listeners exist or there are newly added unhandled promise rejections then go to 2
  7. Next tick loop is done (back we go to C++)

let didCall = false;
process.on('unhandledRejection', () => {
assert(!didCall);
didCall = true;
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 not sure, but, common.mustCall()? ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it checks at exit, this would just keep looping forever until timeout is hit.

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading the code and testing it - I like the behavior and changes. LGTM.

@benjamingr benjamingr removed their assignment Jan 18, 2018
@apapirovski apapirovski added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Jan 18, 2018
@apapirovski
Copy link
Member Author

apapirovski commented Jan 19, 2018

Remove the unnecessary microTasksTickObject for scheduling microtasks
and instead use TickInfo to keep track of whether promise rejections
exist that need to be emitted. Consequently allow the microtasks to
execute on average fewer times, in more predictable manner than
previously.

Simplify unhandled & handled rejection tracking to do more in C++ to
avoid needing to expose additional info in JS.

When new unhandledRejections are emitted within an unhandledRejection
handler, allow the event loop to proceed first instead. This means
that if the end-user code handles all promise rejections on nextTick,
rejections within unhandledRejection now won't spiral into an infinite
loop.
@BridgeAR
Copy link
Member

New CI due to some changed code (If I am not mistaken) https://ci.nodejs.org/job/node-test-pull-request/12619/

@apapirovski
Copy link
Member Author

apapirovski commented Jan 19, 2018

@BridgeAR It was just rebased to make it possible to run the CitGM. But no harm in extra CI :)

@apapirovski
Copy link
Member Author

Landed in d62566e

@apapirovski apapirovski deleted the patch-promise-reject-refactor branch January 21, 2018 17:38
apapirovski added a commit that referenced this pull request Jan 21, 2018
Remove the unnecessary microTasksTickObject for scheduling microtasks
and instead use TickInfo to keep track of whether promise rejections
exist that need to be emitted. Consequently allow the microtasks to
execute on average fewer times, in more predictable manner than
previously.

Simplify unhandled & handled rejection tracking to do more in C++ to
avoid needing to expose additional info in JS.

When new unhandledRejections are emitted within an unhandledRejection
handler, allow the event loop to proceed first instead. This means
that if the end-user code handles all promise rejections on nextTick,
rejections within unhandledRejection now won't spiral into an infinite
loop.

PR-URL: #18207
Fixes: #17913
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
@MylesBorins
Copy link
Contributor

This does not land cleanly on v9.x, should we backport?

@apapirovski
Copy link
Member Author

@MylesBorins v9.x might be missing some preceding commits, I think. I'll have time to look into it at the end of the week or the weekend, swamped with a move right now. Sorry.

@addaleax
Copy link
Member

This seem to apply cleanly on v9.x now, I’m removing the label.

@addaleax addaleax removed backport-requested-v9.x author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Feb 27, 2018
addaleax pushed a commit to addaleax/node that referenced this pull request Feb 27, 2018
Remove the unnecessary microTasksTickObject for scheduling microtasks
and instead use TickInfo to keep track of whether promise rejections
exist that need to be emitted. Consequently allow the microtasks to
execute on average fewer times, in more predictable manner than
previously.

Simplify unhandled & handled rejection tracking to do more in C++ to
avoid needing to expose additional info in JS.

When new unhandledRejections are emitted within an unhandledRejection
handler, allow the event loop to proceed first instead. This means
that if the end-user code handles all promise rejections on nextTick,
rejections within unhandledRejection now won't spiral into an infinite
loop.

PR-URL: nodejs#18207
Fixes: nodejs#17913
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
@addaleax addaleax mentioned this pull request Feb 27, 2018
MayaLekova pushed a commit to MayaLekova/node that referenced this pull request May 8, 2018
Remove the unnecessary microTasksTickObject for scheduling microtasks
and instead use TickInfo to keep track of whether promise rejections
exist that need to be emitted. Consequently allow the microtasks to
execute on average fewer times, in more predictable manner than
previously.

Simplify unhandled & handled rejection tracking to do more in C++ to
avoid needing to expose additional info in JS.

When new unhandledRejections are emitted within an unhandledRejection
handler, allow the event loop to proceed first instead. This means
that if the end-user code handles all promise rejections on nextTick,
rejections within unhandledRejection now won't spiral into an infinite
loop.

PR-URL: nodejs#18207
Fixes: nodejs#17913
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
@codebytere
Copy link
Member

@apapirovski this doesn't land cleanly on v8.x, do you think this makes sense to backport to v8.x?

@apapirovski
Copy link
Member Author

@codebytere Yeah, probably wouldn't hurt. I have a backlog of things I'm supposed to backport anyway. I'll do them all this weekend.

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. promises Issues and PRs related to ECMAScript promises.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

unhandledRejection falls into infinite recursion
7 participants