-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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: add capture function for uncaught exceptions #17159
Conversation
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.
Looks great, as long as CI is happy 👍
doc/api/process.md
Outdated
If the `--abort-on-uncaught-exception` flag is not set, this value is ignored. | ||
|
||
*Note*: If your code uses the deprecated [`domain`][] built-in module, | ||
this flag will be set by that module on whenever domain state changes. |
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 think there's an extra 'on' here. Could just be '[...]module whenever[...]'.
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.
done, thanks for catching!
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.
Sigh, I don't really like this. Basically, this allows users to overwrite the behavior of --abort-on-uncaught-exception
. That could introduce some really hard to debug bugs.
I really like that the complexity is now moved to JavaScript, but we should at least discuss alternatives (or why there are none) for the public API.
edit: only using the ❌ , to indicate that this needs further discussion.
doc/api/process.md
Outdated
when the `--abort-on-uncaught-exception` flag was passed on the command line | ||
or set through [`v8.setFlagsFromString()`][]: | ||
|
||
If the flag was passed to Node and `process.shouldAbortOnUncaughtException` |
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 assume the default value is true
. But it is not clear from the documentation.
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.
Done
It's already allowed via domains though... ? |
lib/domain.js
Outdated
class Domain extends EventEmitter { | ||
function updateShouldAbort() { | ||
process.shouldAbortOnUncaughtException = | ||
stack.every((domain) => domain.listenerCount('error') === 0); |
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, realize this isn't necessarily perf sensitive code but we could just abstract this to a separate top scope function like const checkErrorListenerCount = (domain) => domain.listenerCount('error') === 0
. Take it or leave it, this is a very, very minor nit.
(The main impact of a change like this would be on future contributors more than practical impacts on this specific code.)
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 would expect the perf to be the same, this function doesn’t actually close over any outside variables.
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've tested a similar situation before and it's about 10% difference. I would think it's because the arrow function has to be created separately each time (I don't imagine this is something that V8 can optimize) but it's possible there's another reason that I overlooked.
But as I said, it's not perf sensitive so this was more about steering future contributors in the right direction as far as these type of things go.
Anyway, just ignore me... this took up more time than I intended it to. 😆
Yes. But Also, as I understand it, this will still emit events to |
Sorry, I think one
Uh, I’m not sure which scenario that would be – if
It does when |
@addaleax Could we add a test that confirms the following?
|
@apapirovski The existing tests for uncaught exceptions should cover that, since the default value is |
Yeah, I think I'm just being overcautious. Was trying to account for a situation where enough changes occur that other tests aren't representative anymore but I don't think that's actually possible. That said, I feel like we should at least add a check to confirm the default value of |
@@ -2,7 +2,7 @@ | |||
'use strict'; | |||
const common = require('../common'); | |||
|
|||
// This should make the process not crash even though the flag was passed. | |||
// This should make the process does not crash even though the flag was passed. |
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 think the change might've turned this into a typo? I think the previous version was just referring to the next line in which case it was grammatically correct.
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.
lol yes i just noticed myself 😄
@apapirovski Okay, I’ve added some more explicit tests about those facts |
Sorry. Fixed it. Yes, what you guessed. When
Exactly. When
Yes. But not in the I must express myself very poorly :/ I will try and extract the important parts of this conversation:
No, it isn't because I don't like this because |
@AndreasMadsen Makes sense. I don't have a particularly strong opinion on this. It seems like a satisfactory implementation change might be to use a Symbol instead? |
@AndreasMadsen I don't quite follow, tbh... when a
It might be helpful if you could explain why this is a bad thing in itself? This is a low-level primitive, and if you think it's a good idea we can certainly be more explicit about that in the docs. Also: Coming to think of that, you can already kind of override it anyway,
@apapirovski Use a Symbol for what? If we want to not expose this flag we can just not do that, but I would really prefer to keep working towards a |
I mean, we would still want an internal way to set (I don't have a strong opinion on whether users should change this or not, so my questions/comments are not meant to lean one way or another.) |
@apapirovski If we want this to be internal, we could always just expose it on |
@addaleax Yeah, I just wondered (aloud) if the lowest-effort change would be enough to bridge the gap or if there are other objections to this implementation. Not suggesting it's an optimal way to do it (or isn't). And the main reason I'm chiming in in the first place is that a straight up red X was used here immediately which seems like an indicator of pretty strong opposition. Given that most PRs have 48 hours to land, it would be nice if conversations could be had without resorting to ❌ |
doc/api/process.md
Outdated
@@ -1920,10 +1944,12 @@ cases: | |||
[`process.exitCode`]: #process_process_exitcode | |||
[`process.kill()`]: #process_process_kill_pid_signal | |||
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch | |||
[`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception |
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.
A nit: out of ABC order)
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.
Mh - it's o after c, right?
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 one before it is promise
not process
. Tripped me up the first few times I read 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.
process.kill
process.on
promise.catch
?)
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.
Ah, yup. They look so similar …
doc/api/process.md
Outdated
is `true`, the process will abort when enountering an uncaught exception | ||
(regardless of any [`process.on('uncaughtException')`][] listeners). | ||
|
||
If the flag was passed to Node and `process.shouldAbortOnUncaughtException` |
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.
s/Node/Node.js
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.
done!
doc/api/process.md
Outdated
|
||
If the `--abort-on-uncaught-exception` flag is not set, this value is ignored. | ||
|
||
*Note*: If your code uses the deprecated [`domain`][] built-in module, |
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.
Please void the use of your
in the docs :-)
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.
done!
lib/internal/bootstrap_node.js
Outdated
@@ -412,6 +412,23 @@ | |||
|
|||
return caught; | |||
}; | |||
|
|||
// This is a typed array for faster communication with JS. | |||
const shouldAbortOnUncaughtToggle = process._shouldAbortOnUncaughtToggle; |
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.
If we think there might be more of these kinds of config style toggles, it may be worthwhile creating a single TypedArray that can hold multiple that it exposed via process.binding('config')
rather than introducing one per instance. For now, this is fine, I just would like to avoid having a growing number of one off TypedArray instances.
v8.setFlagsFromString('--abort-on-uncaught-exception'); | ||
// This should make the process not crash even though the flag was passed. | ||
process.shouldAbortOnUncaughtException = false; | ||
process.once('uncaughtException', common.mustCall()); |
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.
My question would be whether this violates user expectations. It might be worthwhile emitting a process warning here so that, at the very least, the user can be aware of what is happening. It is entirely possible and likely that some userland module deep in the dependency tree could set this property and a user would have no idea why --abort-on-uncaught-exception
is not working.
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 -1 on a process warning; if there is a shouldAbortOnUncaughtException = false
statement, that’s not an accident.
It is entirely possible and likely that some userland module deep in the dependency tree could set this property
This flag isn’t really relevant for non-diagnostics tooling, so I would be surprised if that were true.
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.
...so I would be surprised if that were true
I've been surprised many times by what I've found in userland ;-)
If we're not going to warn on this, then the documentation for the --abort-on-uncaught-exception
flag should be updated to clearly indicate that the argument can be short circuited by this new process
property. If nothing else, that gives users something to go look for before filing the bug report here that the flag isn't working ;-)
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.
Yeah, sure. 👍
god, this is a disaster. do y'all not have anyone doing quality review any more? |
@alice0meta If you have feedback that addresses the substance of this PR, I’d really like to hear it – this was not an API that anybody ever really wanted, it’s a means to an end, and it’s young enough to be considered not set in stone. But otherwise I’m not sure what exactly you’re trying to tell us with that comment? What exactly do you think went wrong here? |
nod. sorry, um. i found this api by looking at the table of contents in https://nodejs.org/api/process.html. but it's a hack whose purpose is to make it easier to use certain legacy systems (right?). this slows down and/or confuses people going to the node api to find out how to handle uncaught exceptions. but this could be largely remedied if, say, it was moved to the end & automatically hidden, like i don't have experience with this kind of layout, though, so i imagine the right way to show this to the user might be somewhat different than this idea. i just think it at least needs to be expressed in the documentation in a way that doesn't slow the user down - it's part of the intended understanding of the function, right? |
setUncaughtExceptionCapture -> setUncaughtExceptionCaptureCallback process.setUncaughtExceptionCaptureCallback and its docs were originally added in #17159 PR-URL: #21523 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
setUncaughtExceptionCapture -> setUncaughtExceptionCaptureCallback process.setUncaughtExceptionCaptureCallback and its docs were originally added in #17159 PR-URL: #21523 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
I just stumbled on this PR while reading #17143. A few questions and comments came up to my mind while trying to understand the goal(s) of this PR and its impact on users:
I haven't read every single comment in this PR, and as result I may be missing the nuances of what actual impact it has on users. But if point 2) is true, I would like to express my strong opposition to this change in its current form, and I would like to know whether we could explore an alternative that would achieve the same goals without having the same impact on the Thoughts? |
From a process perspective not tagging the relative groups is unusual, but we do not have official code owners and enough collaborators LGTM'ed this. So, this is ok (we might want to change our processes but that's a different topic). Note that 3 @nodejs/diagnostics members gave a looking good. cc @nodejs/tsc The why of this PR is to ultimately be able to implement something like Domain outside of core. There are a significant number of people using
I would say that this is extremely easy to disable, just add a preload script and override that function. Currently it's the only way to use promises and async/await safely (see https://github.com/mcollina/make-promises-safe for more details). I would recommend to do a |
@misterdjules This PR replaces complexity that already existed in domains. It thus doesn't change anything about the In the end, this PR doesn't add or remove anything of consequence. I think we have just made it more transparent how domains work and why it is often a bad idea. |
I'm aware that this PR followed the documented process for contributions and changes. My goal with that comment was to give feedback that socializing such potentially impactful changes outside of the circle of usual Node.js core reviewers is important. #17143 has also been discussing radical changes to the domains module (including its removal from core) without notifying teams, and instead individuals. In general I think there is room for improvement on the communication aspects of those impactful PRs.
Not all members of the diagnostics group (which has a lot more members than 3) share the same perspective or have the same use cases. Moreover, being part of a given group doesn't mean that they knew about that PR because they're part of that group, or that they were providing feedback with the interests of that group in mind.
Ah, that's good to know! I definitely understand the broader use case better now, thanks for clarifying 👍
My overall impression is that resorting to using such mechanisms to disable public and documented core APIs to ensure consistency of the runtime's behavior is a sign of potential design issues and leads to worse developer experience. It's difficult to measure and quantify though. The Practically it's not much of a difference, because anyone could build a native module that uses that API (like e.g. It seems that userland module authors could still be using that existing API to experiment with alternatives to domains. Is that not the case? |
It is not, relying on V8 APIs is hard across Node.js versions and runtimes. Moreover, a significant chunk of the Node.js users prefer JS-only modules. Exposing a V8 API seems fair, considering how much is domain still being used in the ecosystem. |
What it changes is that it significantly lowers the barrier to entry for completely overriding the behavior of the runtime when an exception is thrown and not caught. The impact of that is difficult to measure and quantify, but I think this could have a negative impact on post-mortem debugging use cases. If that's the conscious trade-off the project is willing to make to allow for easier innovation in the effort to come up with an alternative to domains, that's fine. However I'm not sure that this newly added API is necessary for that, unless the existing |
I agree with that if we consider the whole set of V8 APIs used across all modules, but that API has been there for a while and never changed (unless I'm missing something), and although it wouldn't be reasonable to presume it will never change, I wouldn't be surprised if it'd turn out to be as stable as any other userland API.
I would agree with that 👍
What V8 API are you referring to? |
|
@mcollina But that |
Maybe to add to this, since it sounds like you see a risk of
I think that’s a positive feature – even if we expect it and want it to be used infrequently. It enables use cases that would previously only have been accessible through semi-private API (e.g. overriding
You’re right, and the documentation should be more explicit about the facts that |
I'm not necessarily seeing a risk of domains being removed from core. It is my understanding based on the status of #17143 that this won't happen before a functionally equivalent alternative is made available (but please correct me if that's an incorrect assumption). I'm more concerned about the semantics of
If the main use cases are to expose a hook into If the main use case is to allow userland implementations of the domain core module though, then it makes sense to expose both at the same time, and at this point it becomes a question of whether we believe that allowing userland implementations of domain-like modules is more desirable than guaranteeing the behavior of My opinion is that I'd rather have a runtime that guarantees the behavior of The overarching thought behind my comments is that continuing to decouple further the error handling model from the core runtime seems like this could lead to making it very difficult to reason about what the actual error handling model is at any point in time depending of what code has executed. I think that this is an area where we should optimize for consistency instead of flexibility. |
I'm experimenting with implementing a user-land Is that correct? If so, is that intentional and is the intention to have users either use the user-land or the core module consistently for all their dependencies? If that's not intentional, does it seem feasible to you to make that |
@misterdjules Yes, I think so.
I’m not sure, but it’s definitely something that could be changed if we want to. The main difference between the capture callback and |
Thanks for the helpful response, I really appreciate it!
Do you mean that we shouldn't allow multiple callbacks to be called on uncaught exception? Is there a problem with allowing that? |
@misterdjules I might be wrong, but that seems to be the most sensible semantics? The |
If we consider the core However if we're thinking of enabling user-land domain-like modules, they would have to co-exist with the core Is the rationale that this use case would be rare and that applications would either use only the core |
@misterdjules I guess the question is more, like: If people use both the built-in |
Right, that's the kind of questions I'm wondering about. Currently I'm thinking: why not both? But I'm not sure I settled on an answer yet. |
Introduce
process.shouldAbortOnUncaughtException
to control--abort-on-uncaught-exception
behaviour, and implement some of the domains functionality on top of it.Refs: #17143
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
process