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

src: exit on gc unhandled rejection #20097

Closed
wants to merge 6 commits into from

Conversation

BridgeAR
Copy link
Member

@BridgeAR BridgeAR commented Apr 17, 2018

This is a minimal implementation of exiting on garbage collected unhandled rejections.

As soon as a unhandled rejection is collected, it will trigger a fatal exception and exit with code 1 and print the error.
It will keep emitting warnings as it used to to and the user will be notified in case there are false negatives for the GC (a truly unhandled rejection is not garbage collected).

The warnings have quite some room for performance optimization but I wanted to keep this PR to the bare minimum so it is only about the functionality itself. I plan on working on optimizing the performance after this landed.

This work is based on former work from @Fishrock123 and @addaleax.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@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 Apr 17, 2018
@BridgeAR

This comment has been minimized.

@BridgeAR BridgeAR added this to the 10.0.0 milestone Apr 17, 2018
@BridgeAR BridgeAR added the semver-major PRs that contain breaking changes and should be released in the next major version. label Apr 17, 2018
@apapirovski

This comment has been minimized.

@BridgeAR

This comment has been minimized.

@BridgeAR BridgeAR removed this from the 10.0.0 milestone Apr 17, 2018
src/node.cc Outdated Show resolved Hide resolved
@jasnell

This comment has been minimized.

src/node.cc Outdated Show resolved Hide resolved
@@ -426,23 +426,23 @@
emitAfter
} = NativeModule.require('internal/async_hooks');

process._fatalException = function(er) {
process._fatalException = function(er, fromPromise) {
Copy link
Member

Choose a reason for hiding this comment

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

The only point of fromPromise is skipping the uncaughtException handler – is that correct? If so, can you explain the motivation behind it?

(Also, if that is correct: Could we name it something like skipUncaughtExceptionListener or so, to be a bit more descriptive?)

Copy link
Member Author

Choose a reason for hiding this comment

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

That is correct. In this case I just used the original implementation. I personally have no strong opinion about having a uncaughtException in this case. In a way it would make sense to have that.

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 +1 on calling uncaughtException handlers on unhandled rejections. I think it would be very surprising to not have them called - especially for APMs.

Copy link
Member

Choose a reason for hiding this comment

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

Note that this would also mean we'd have to deal with the case people chose to ignore the error rather than exit the process - like they can in an "unhandledRejection" handler.

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 +1 to route unhandled rejection into 'uncaughtException' at this point.

Copy link
Member Author

Choose a reason for hiding this comment

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

@ljharb that would not be possible with the suggested implementation. What could be done is to wrap the error into another error that reports about being a unhandled rejection.

@benjamingr that would not be the case with the suggested change. It would work as I explained above. So having a unhandledRejection handler will not prevent the process to exit. That would completely depend on uncaughtException.

Copy link
Member

Choose a reason for hiding this comment

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

@BridgeAR I understand and I don't think that's correct semantics. I think it would surprise users - you'd expect to be able to intercept any unhandledRejection. It's very common to use it to throw and cause the uncaughtException today - or suppress termination in certain cases like:

process.on('unhandledRejection', err => {
  if (err.isImportant) log(err);
  if (err.isSpecificKind) return; // not an error, a bug with some library I'm using
  throw err; 
});

Copy link
Member Author

Choose a reason for hiding this comment

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

It is possible to intercept the unhandled rejections. But the error on GC is a different story.

By the way: without routing the unhandled rejection to uncaught exception, it would not be possible to stop the process from exiting.

It is a anti pattern to continue the program in case a exception is thrown. The pattern about ignoring specific errors is not something we should promote out of my perspective. In case a library is handling something wrong, the library should be fixed or not used.

This comment was marked as outdated.

Copy link
Member

Choose a reason for hiding this comment

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

To anyone reading - we went on to gather some real user data and the contents of this thread is invalid. See the proposed semantics in nodejs/promise-use-cases#27

src/node.cc Outdated Show resolved Hide resolved
src/node.cc Outdated Show resolved Hide resolved
src/node.cc Outdated Show resolved Hide resolved
@Fishrock123
Copy link
Contributor

It appears to me that this has hole that original doesn't:

The information gets lots entirely if your process exists prematurely. Even if the promise remains unhandled to process end.

That's why I did iteration on process end to check if there were unhandled rejections lying around. GC on promises is so slow that it is only likely to trigger on long-running applications, even if silent error(s) occurred and state is undefined in shorter scripts.

@Fishrock123
Copy link
Contributor

I suppose that could be done separately, but it seems deeply connected to the end behavior to me.

@BridgeAR BridgeAR force-pushed the proper-unhandled-rejections branch from f9bcf1c to 333b0fb Compare April 17, 2018 17:11
@BridgeAR
Copy link
Member Author

BridgeAR commented Apr 17, 2018

@Fishrock123 I am not sure I can follow. In what way does the information get lost? Can you give me an example? I see your point but I would rather get this in without that first. Adding something like that later on should just be semver-minor as far as I can tell, since it would not interfere with the regular program flow and just log stuff on exit.

@BridgeAR
Copy link
Member Author

I addressed some comments.

src/node.cc Outdated Show resolved Hide resolved
@BridgeAR
Copy link
Member Author

Just a heads up: I am currently flooded and I am going to work on this in one or two days again.

@devsnek devsnek mentioned this pull request Jul 17, 2018
4 tasks
@mhdawson
Copy link
Member

@BridgeAR what's your thought on the suggestion that this be landed as experimental to start? It seems like it might make sense to me.

@benjamingr
Copy link
Member

@mhdawson

@BridgeAR what's your thought on the suggestion that this be landed as experimental to start? It seems like it might make sense to me.

The current plan is to land this as opt-in to start with users having to explicitly define an environment variable or use a command-line flag. The idea is to change this behaviour to the default at a later point.


Setting this environment variable allows fine grained control over the behavior
of unhandled rejections. This may be set to either one of `SILENT`, `WARN`,
`ERROR_ON_GC`, `ERROR` or `STRICT` while the default behavior without using the
Copy link
Member

Choose a reason for hiding this comment

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

just a nit... make the values lower-case. They tend to be easier for users. --unhandled-rejections=error_on_gc

Copy link
Member Author

Choose a reason for hiding this comment

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

They will be accepted in all cases but it should be easier to read them being upper case.

It should have received a variable. Instead, it was set to a string
that was coerced to a integer later on. This fixes it by correctly
passing through the variable value.
This adds a fast path to the unhandledRejections and handledRejections
to actually stop trying to emit something if no hook is attached.
It is not necessary to run the microtasks when an unhandledRejection
hook is attached.
fixup: undo deprecation change
@jasnell
Copy link
Member

jasnell commented Oct 16, 2018

Ping @BridgeAR ... given the discussion last week, do you plan on updating this PR or opening a new one with the agreed upon updates?

@BridgeAR
Copy link
Member Author

I am fine either way. I would likely just go ahead and update the PR.

@ljharb
Copy link
Member

ljharb commented Oct 16, 2018

Can someone summarize what updates were agreed upon?

@devsnek
Copy link
Member

devsnek commented Oct 16, 2018

@ljharb we agreed to move forward on a silent mode, warning mode, and always exit mode. however four people (including myself) disagreed with the existence of gc mode, and no one in the room could think of any good reason why anyone would want to use a gc mode.

i don't recall though if we agreed on a default... maybe it happened after the zoom cut out.

@targos
Copy link
Member

targos commented Oct 16, 2018

Add an opt-in flag to let the user choose between 3 ways for Node to react to unhandled rejections:

  • Silent: Node does nothing
  • Warn: Node emits a warning. This would be similar to the current behavior but with a plain warning instead of a deprecation warning
  • Throw: Node throws an uncaughtException

@mmarchini
Copy link
Contributor

Actually, when @BridgeAR asked us who disagreed with Exit on GC there were more than 4 people with their hands up.

i don't recall though if we agreed on a default

We agreed that default is out of scope for this PR and should be discussed separately.

@devsnek
Copy link
Member

devsnek commented Oct 16, 2018

@mmarchini thanks for the correction, i'm glad it was more :)

@boneskull
Copy link
Contributor

Dear Santa,

I haven't written in awhile, sorry. I found myself at this PR after clicking through three previously-closed PRs all the way back to April 2016. Then I read through nodejs/promises#26, where it sounded like abort-on-GC was accepted as a fair solution.

A full two years later, when this PR was opened, it was still a fair solution.

Now, I'm reading Node.js need a flag to choose between three behaviors; one of which is the current behavior, another is seemingly the same as --no-warnings, and the other is... really confusing. I think there's a fourth, which is "abort on Promise" that some people seem to like.

This is comically frustrating. If collaborators can't even agree on which behavior(s) to provide, how could they pick a default?

So here's my xmas list. I realize this is a big ask.

  1. Collaborators agree that Node's current behavior is distasteful and confusing for a wide range of Node.js users, especially those new to Promises.
  2. Collaborators would understand that anything more complex than an "on/off" behavior would negatively impact module maintainers (and the users of those modules) who must add code to account for the behaviors.
  3. Collaborators put aside personal preferences, opinions or habits, and instead refer to the previous two items on this list.
  4. @BridgeAR or another generous soul would resume work on this PR.
  5. Node.js' TSC:
    1. Recognizes this has gone on for far too long, at the expense of Node.js' users.
    2. Commits to a solution per their decision-making process.
    3. Commits to a plan to introduce new default behavior after time behind an experimental flag.

your servant in darkness,
Chris

P.S. If I don't drop dead from embarrassment, you will find milk & cookies on the counter next to the whiskey and aspirin.

@BridgeAR
Copy link
Member Author

I opened a alternative PR: #26599

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++. notable-change PRs with changes that should be highlighted in changelogs. process Issues and PRs related to the process subsystem. semver-major PRs that contain breaking changes and should be released in the next major version. wip Issues and PRs that are still a work in progress.
Projects
None yet
Development

Successfully merging this pull request may close these issues.