-
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
src: fix MakeCallback error handling #4507
Conversation
/cc @bajtos |
Why |
@indutny Haven't investigated as to why that's the case. Just noticed it while testing. Could be a bug in v8? Header documentation doesn't indicate that control will never return to native. |
@trevnorris indeed, it should just capture the error. What exactly do you mean by "control will never return to native"? |
@indutny What I mean is that code execution will never return to the native API. IIRC (will have to double check) but I believe this is even the case if |
This PR is incorrect. The |
62f58b8
to
be27b71
Compare
@misterdjules /cc you on this since domains are acting all strange. will post more info and would like whatever feedback you may have. |
be27b71
to
eb72544
Compare
@@ -201,42 +203,49 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, | |||
Local<Value> enter_v = domain->Get(env()->enter_string()); | |||
if (enter_v->IsFunction()) { | |||
enter_v.As<Function>()->Call(domain, 0, nullptr); | |||
if (try_catch.HasCaught()) | |||
if (try_catch.HasCaught()) { | |||
FatalException(env()->isolate(), try_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.
If try_catch.SetVerbose(true)
was called, wouldn't FatalException
be called anyway?
eb72544
to
edade11
Compare
@misterdjules Here's the diff to demonstrate the issue: diff --git a/src/async-wrap.cc b/src/async-wrap.cc
index c9f5caa..fbff146 100644
--- a/src/async-wrap.cc
+++ b/src/async-wrap.cc
@@ -216,10 +216,6 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
Local<Value> ret = cb->Call(context, argc, argv);
- if (try_catch.HasCaught()) {
- return Undefined(env()->isolate());
- }
-
if (ran_init_callback() && !post_fn.IsEmpty()) {
try_catch.SetVerbose(false);
post_fn->Call(context, 0, nullptr);
@@ -237,6 +233,10 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
}
}
+ if (try_catch.HasCaught()) {
+ return Undefined(env()->isolate());
+ }
+
Environment::TickInfo* tick_info = env()->tick_info();
if (tick_info->in_tick()) { And here's the script: 'use strict';
const async_wrap = process.binding('async_wrap');
const domain = require('domain');
const crypto = require('crypto');
const d = domain.create();
function noop() { }
async_wrap.setupHooks(noop, noop, noop);
async_wrap.enable();
d.on('error', function() { });
d.run(function() {
crypto.randomBytes(4, function() { throw new Error('poo') });
}); This causes the application to abort. This is because the Demonstrating that the |
} | ||
|
||
if (has_domain) { | ||
Local<Value> exit_v = domain->Get(env()->exit_string()); | ||
if (exit_v->IsFunction()) { | ||
exit_v.As<Function>()->Call(domain, 0, nullptr); | ||
if (try_catch.HasCaught()) | ||
try_catch.Reset(); | ||
if (try_catch.HasCaught()) { |
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 I understand correctly, try_catch.HasCaught()
will always return false
after a call to TryCatch::Reset
, so I'm not sure I understand what the use case is.
@trevnorris OK, I think I understand the intent of this PR. Can you please update the original description of this PR + the commit message according to what you described in your latest comment? Otherwise understanding the intent of this PR is very difficult. Now I also understand better one of the questions you had in #node-dev:
which I interpret now as:
I would think that the best one can do in this case is to either:
In other words, one should not expect the node runtime to be able to continue its execution normally. Moreover, when a domain error handler is called, all domains on the stack are cleared. So I'm not sure it's useful or even if it makes sense to call I'm not familiar with the AsyncWrap subsystem, but I'd also be interested to know what the semantics are regarding post hooks and errors thrown within an async callback. Do we consider the async operation to be done, or failed? Do we make a distinction between the two? Basically, I'm wondering if the current |
The main purpose of adding I vaguely remember that "break on uncaught error" may have stopped working in later v0.11 versions, i.e. it's possible it worked only in few v0.11 versions and never else. Just my two cents to give a bit more context, I am afraid I don't have enough time to take a deeper look at this anytime soon. |
@misterdjules My original comment about
This is important. So are you saying it's not necessary to call the domain's
If an async wrap callback throws the application aborts. I have no idea what the application of the state could be in this case, and since it's currently a "private" API wanted to make sure this never happened.
If it's the case that the domain's @bajtos Thanks for the info. With that context now I'll know what to look for. Nevermind my concerns about |
1a68317
to
160b3d1
Compare
@trevnorris the windows failure was due to work in progress in CI. Relaunched here: https://ci.nodejs.org/job/node-test-commit-windows-fanned/815/ |
57702ae
to
f9af215
Compare
@trevnorris I still don't understand what this PR intends to fix in terms of error handling. Is the purpose of this PR to fix the issue described in https://gist.github.com/misterdjules/fac76caa775b3ad63ac8? |
@misterdjules This PR has shifted a couple times now as I've been working on it. The wording of "fix error handling" is meant to be "fix timing of return in |
After attempting to use ReThrow() and Reset() there were cases where firing the domain's error handlers was not happening. Or in some cases reentering MakeCallback would still cause the domain enter callback to abort (because the error had not been Reset yet). In order for the script to properly stop execution when a subsequent call to MakeCallback throws it must not be located within a TryCatch. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Make sure that calling MakeCallback multiple times within the same stack does not allow the nextTickQueue or MicrotaskQueue to be processed in any more than the first MakeCallback call. Check that domains enter/exit poperly with multiple MakeCallback calls and that errors are handled as expected Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Implementations of error handling between node::MakeCallback() and AsyncWrap::MakeCallback() do not return at the same point. Make both executions work the same by moving the early return if there's a caught exception just after the AsyncWrap post callback. Since the domain's call stack is cleared on a caught exception there is no reason to call its exit() callback. Remove the SetVerbose() statement in the AsyncWrap pre/post callback calls since it does not affect the callback call. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Add a scope that will allow MakeCallback to know whether or not it's currently running. This will prevent nextTickQueue and the MicrotaskQueue from being processed recursively. It is also required to wrap the bootloading stage since it doesn't run within a MakeCallback. Ref: nodejs/node-v0.x-archive#9245 Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Environment::TickInfo::last_threw() is no longer in use. Also pass Isolate to few methods and fix whitespace alignment. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
After attempting to use ReThrow() and Reset() there were cases where firing the domain's error handlers was not happening. Or in some cases reentering MakeCallback would still cause the domain enter callback to abort (because the error had not been Reset yet). In order for the script to properly stop execution when a subsequent call to MakeCallback throws it must not be located within a TryCatch. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Make sure that calling MakeCallback multiple times within the same stack does not allow the nextTickQueue or MicrotaskQueue to be processed in any more than the first MakeCallback call. Check that domains enter/exit poperly with multiple MakeCallback calls and that errors are handled as expected Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Implementations of error handling between node::MakeCallback() and AsyncWrap::MakeCallback() do not return at the same point. Make both executions work the same by moving the early return if there's a caught exception just after the AsyncWrap post callback. Since the domain's call stack is cleared on a caught exception there is no reason to call its exit() callback. Remove the SetVerbose() statement in the AsyncWrap pre/post callback calls since it does not affect the callback call. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Add a scope that will allow MakeCallback to know whether or not it's currently running. This will prevent nextTickQueue and the MicrotaskQueue from being processed recursively. It is also required to wrap the bootloading stage since it doesn't run within a MakeCallback. Ref: nodejs/node-v0.x-archive#9245 Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Environment::TickInfo::last_threw() is no longer in use. Also pass Isolate to few methods and fix whitespace alignment. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
After attempting to use ReThrow() and Reset() there were cases where firing the domain's error handlers was not happening. Or in some cases reentering MakeCallback would still cause the domain enter callback to abort (because the error had not been Reset yet). In order for the script to properly stop execution when a subsequent call to MakeCallback throws it must not be located within a TryCatch. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Make sure that calling MakeCallback multiple times within the same stack does not allow the nextTickQueue or MicrotaskQueue to be processed in any more than the first MakeCallback call. Check that domains enter/exit poperly with multiple MakeCallback calls and that errors are handled as expected Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Implementations of error handling between node::MakeCallback() and AsyncWrap::MakeCallback() do not return at the same point. Make both executions work the same by moving the early return if there's a caught exception just after the AsyncWrap post callback. Since the domain's call stack is cleared on a caught exception there is no reason to call its exit() callback. Remove the SetVerbose() statement in the AsyncWrap pre/post callback calls since it does not affect the callback call. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Add a scope that will allow MakeCallback to know whether or not it's currently running. This will prevent nextTickQueue and the MicrotaskQueue from being processed recursively. It is also required to wrap the bootloading stage since it doesn't run within a MakeCallback. Ref: nodejs/node-v0.x-archive#9245 Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Environment::TickInfo::last_threw() is no longer in use. Also pass Isolate to few methods and fix whitespace alignment. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
After attempting to use ReThrow() and Reset() there were cases where firing the domain's error handlers was not happening. Or in some cases reentering MakeCallback would still cause the domain enter callback to abort (because the error had not been Reset yet). In order for the script to properly stop execution when a subsequent call to MakeCallback throws it must not be located within a TryCatch. Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
Make sure that calling MakeCallback multiple times within the same stack does not allow the nextTickQueue or MicrotaskQueue to be processed in any more than the first MakeCallback call. Check that domains enter/exit poperly with multiple MakeCallback calls and that errors are handled as expected Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
The final goal of this PR is to allow
MakeCallback
(in bothnode::
andAsyncWrap::
) to be reentrant. Reason is because theHTTPParser
may enter the JS call stack initially, or be called while already in a stack (see @indutny's PR #4509). Also so native addon developers can always usenode::MakeCallback
without the worry of code being executed in the wrong order (specifically in relation to usage ofnextTick
or theMicrotaskQueue
).To do this I first made sure the two implementations of
MakeCallback
were the same. Previously they would return at different points in the case of caught exception.Finally was the addition of
AsyncCallbackScope
(admittedly the concept was taken from Fedor's PR above) to allowMakeCallback
to be called recursively without each subsequent call from processing thenextTickQueue
or theMicrotaskQueue
.I am working with @misterdjules on the proper way of handling domains. So this PR is currently a WIP.
Ref: nodejs/node-v0.x-archive#9245
R=@bnoordhuis
R=@indutny