-
Notifications
You must be signed in to change notification settings - Fork 30.5k
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
Changes from all commits
575b84e
e919224
f86a3a2
5e30478
95afe28
ab6fe59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,8 +173,8 @@ void LoadAsyncWrapperInfo(Environment* env) { | |
|
||
|
||
Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, | ||
int argc, | ||
Local<Value>* argv) { | ||
int argc, | ||
Local<Value>* argv) { | ||
CHECK(env()->context() == env()->isolate()->GetCurrentContext()); | ||
|
||
Local<Function> pre_fn = env()->async_hooks_pre_function(); | ||
|
@@ -184,6 +184,8 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, | |
Local<Object> domain; | ||
bool has_domain = false; | ||
|
||
Environment::AsyncCallbackScope callback_scope(env()); | ||
|
||
if (env()->using_domains()) { | ||
Local<Value> domain_v = context->Get(env()->domain_string()); | ||
has_domain = domain_v->IsObject(); | ||
|
@@ -194,52 +196,45 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, | |
} | ||
} | ||
|
||
TryCatch try_catch(env()->isolate()); | ||
try_catch.SetVerbose(true); | ||
|
||
if (has_domain) { | ||
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()) | ||
return Undefined(env()->isolate()); | ||
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) { | ||
FatalError("node::AsyncWrap::MakeCallback", | ||
"domain enter callback threw, please report this"); | ||
} | ||
} | ||
} | ||
|
||
if (ran_init_callback() && !pre_fn.IsEmpty()) { | ||
try_catch.SetVerbose(false); | ||
pre_fn->Call(context, 0, nullptr); | ||
if (try_catch.HasCaught()) | ||
if (pre_fn->Call(context, 0, nullptr).IsEmpty()) | ||
FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); | ||
try_catch.SetVerbose(true); | ||
} | ||
|
||
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); | ||
if (try_catch.HasCaught()) | ||
if (post_fn->Call(context, 0, nullptr).IsEmpty()) | ||
FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); | ||
try_catch.SetVerbose(true); | ||
} | ||
|
||
if (ret.IsEmpty()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be worth mentioning in a comment that this early return is very important because of the way Without a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. I'll add it to both locations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @misterdjules actually I'm a little confused. without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verified this by placing the early return after the domain exit call and tested with the following: 'use strict';
const domain = require('domain');
const d = domain.create();
const async_wrap = process.binding('async_wrap');
async_wrap.setupHooks(function() {
}, function() {
process._rawDebug('before');
}, function() {
process._rawDebug('after');
});
async_wrap.enable();
d.on('error', function() {
process._rawDebug('d errored');
});
d.run(function() {
require('crypto').randomBytes(1024, function() {
throw new Error('crap');
});
}); |
||
return Undefined(env()->isolate()); | ||
} | ||
|
||
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()) | ||
return Undefined(env()->isolate()); | ||
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) { | ||
FatalError("node::AsyncWrap::MakeCallback", | ||
"domain exit callback threw, please report this"); | ||
} | ||
} | ||
} | ||
|
||
Environment::TickInfo* tick_info = env()->tick_info(); | ||
|
||
if (tick_info->in_tick()) { | ||
if (callback_scope.in_makecallback()) { | ||
return ret; | ||
} | ||
|
||
|
@@ -252,14 +247,7 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, | |
return ret; | ||
} | ||
|
||
tick_info->set_in_tick(true); | ||
|
||
env()->tick_callback_function()->Call(process, 0, nullptr); | ||
|
||
tick_info->set_in_tick(false); | ||
|
||
if (try_catch.HasCaught()) { | ||
tick_info->set_last_threw(true); | ||
if (env()->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { | ||
return Undefined(env()->isolate()); | ||
} | ||
|
||
|
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 not setting verbose to
false
here means that errors thrown from with async pre-hooks would be forwarded to the active domain's error handler. Do you have time to verify that theory? If that's the case, I'm not sure that makes sense, and what the right thing to do is.Setting verbose to
false
also means that the debugger wouldn't catch any exception in async pre-hooks, so it might not be ideal too.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.
Confirmed:
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 trying to reproduce that for
node::MakeCallback
with the following code:doesn't seem to trigger async hooks (note the change from calling
fs.stat
tosetImmediate
). Am I missing something?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.
In the init callback you have to put
this._asyncQueue = {}
. Since it doesn't tap into theAsyncWrap
class used that long ago to trigger the remaining callbacks. Not a good solution, but haven't had a chance to rethink it.As for the
setImmediate()
. timers don't currently work ATM. Will require hacking the JS and have been putting that off hoping I'd find a better solution.