From b5319a56214dc0b94159a3a0623e648e9cc4cf4a Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Thu, 11 Feb 2016 13:09:52 -0700 Subject: [PATCH 01/18] src: fix MakeCallback error handling 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. PR-URL: https://github.com/nodejs/node/pull/4507 Reviewed-By: Fedor Indutny --- src/async-wrap.cc | 13 +++++-------- src/node.cc | 13 +++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/async-wrap.cc b/src/async-wrap.cc index c9f5caad1e4ea8..29ea139f5f91c0 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -207,25 +207,22 @@ Local AsyncWrap::MakeCallback(const Local cb, } if (ran_init_callback() && !pre_fn.IsEmpty()) { - try_catch.SetVerbose(false); pre_fn->Call(context, 0, nullptr); if (try_catch.HasCaught()) FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); - try_catch.SetVerbose(true); } Local 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()) FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); - try_catch.SetVerbose(true); + } + + // If the return value is empty then the callback threw. + if (ret.IsEmpty()) { + return Undefined(env()->isolate()); } if (has_domain) { diff --git a/src/node.cc b/src/node.cc index 8264bfe8bba8a3..b048166cd9d8df 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1165,21 +1165,22 @@ Local MakeCallback(Environment* env, } if (ran_init_callback && !pre_fn.IsEmpty()) { - try_catch.SetVerbose(false); pre_fn->Call(object, 0, nullptr); if (try_catch.HasCaught()) FatalError("node::MakeCallback", "pre hook threw"); - try_catch.SetVerbose(true); } Local ret = callback->Call(recv, argc, argv); if (ran_init_callback && !post_fn.IsEmpty()) { - try_catch.SetVerbose(false); post_fn->Call(object, 0, nullptr); if (try_catch.HasCaught()) FatalError("node::MakeCallback", "post hook threw"); - try_catch.SetVerbose(true); + } + + // If the return value is empty then the callback threw. + if (ret.IsEmpty()) { + return Undefined(env->isolate()); } if (has_domain) { @@ -1191,10 +1192,6 @@ Local MakeCallback(Environment* env, } } - if (try_catch.HasCaught()) { - return Undefined(env->isolate()); - } - if (!env->KickNextTick()) return Undefined(env->isolate()); From 19dfb2adc1424326348fc2216470bf3b003ab1e1 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Tue, 5 Jan 2016 15:33:21 -0700 Subject: [PATCH 02/18] src: add AsyncCallbackScope 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: https://github.com/nodejs/node-v0.x-archive/issues/9245 PR-URL: https://github.com/nodejs/node/pull/4507 Reviewed-By: Fedor Indutny --- src/async-wrap.cc | 8 +++----- src/env-inl.h | 15 +++++++++++++++ src/env.cc | 8 ++------ src/env.h | 16 +++++++++++++++- src/node.cc | 9 +++++++-- src/node_http_parser.cc | 4 +++- src/node_internals.h | 2 -- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 29ea139f5f91c0..01dcaf277c1840 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -184,6 +184,8 @@ Local AsyncWrap::MakeCallback(const Local cb, Local domain; bool has_domain = false; + Environment::AsyncCallbackScope callback_scope(env()); + if (env()->using_domains()) { Local domain_v = context->Get(env()->domain_string()); has_domain = domain_v->IsObject(); @@ -236,7 +238,7 @@ Local AsyncWrap::MakeCallback(const Local cb, Environment::TickInfo* tick_info = env()->tick_info(); - if (tick_info->in_tick()) { + if (callback_scope.in_makecallback()) { return ret; } @@ -249,12 +251,8 @@ Local AsyncWrap::MakeCallback(const Local 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); return Undefined(env()->isolate()); diff --git a/src/env-inl.h b/src/env-inl.h index f73e9c6ba2000a..eebb68eb463e93 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -88,6 +88,20 @@ inline void Environment::AsyncHooks::set_enable_callbacks(uint32_t flag) { fields_[kEnableCallbacks] = flag; } +inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env) + : env_(env) { + env_->makecallback_cntr_++; +} + +inline Environment::AsyncCallbackScope::~AsyncCallbackScope() { + env_->makecallback_cntr_--; + CHECK_GE(env_->makecallback_cntr_, 0); +} + +inline bool Environment::AsyncCallbackScope::in_makecallback() { + return env_->makecallback_cntr_ > 1; +} + inline Environment::DomainFlag::DomainFlag() { for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; } @@ -210,6 +224,7 @@ inline Environment::Environment(v8::Local context, using_domains_(false), printed_error_(false), trace_sync_io_(false), + makecallback_cntr_(0), async_wrap_uid_(0), debugger_agent_(this), http_parser_buffer_(nullptr), diff --git a/src/env.cc b/src/env.cc index e28866efd06894..8fa6e5c43afe06 100644 --- a/src/env.cc +++ b/src/env.cc @@ -57,10 +57,10 @@ void Environment::PrintSyncTrace() const { } -bool Environment::KickNextTick() { +bool Environment::KickNextTick(Environment::AsyncCallbackScope* scope) { TickInfo* info = tick_info(); - if (info->in_tick()) { + if (scope->in_makecallback()) { return true; } @@ -73,15 +73,11 @@ bool Environment::KickNextTick() { return true; } - info->set_in_tick(true); - // process nextTicks after call TryCatch try_catch; try_catch.SetVerbose(true); tick_callback_function()->Call(process_object(), 0, nullptr); - info->set_in_tick(false); - if (try_catch.HasCaught()) { info->set_last_threw(true); return false; diff --git a/src/env.h b/src/env.h index 1531d9911e310b..8ae681ff43c86f 100644 --- a/src/env.h +++ b/src/env.h @@ -294,6 +294,19 @@ class Environment { DISALLOW_COPY_AND_ASSIGN(AsyncHooks); }; + class AsyncCallbackScope { + public: + explicit AsyncCallbackScope(Environment* env); + ~AsyncCallbackScope(); + + inline bool in_makecallback(); + + private: + Environment* env_; + + DISALLOW_COPY_AND_ASSIGN(AsyncCallbackScope); + }; + class DomainFlag { public: inline uint32_t* fields(); @@ -446,7 +459,7 @@ class Environment { inline int64_t get_async_wrap_uid(); - bool KickNextTick(); + bool KickNextTick(AsyncCallbackScope* scope); inline uint32_t* heap_statistics_buffer() const; inline void set_heap_statistics_buffer(uint32_t* pointer); @@ -541,6 +554,7 @@ class Environment { bool using_domains_; bool printed_error_; bool trace_sync_io_; + size_t makecallback_cntr_; int64_t async_wrap_uid_; debugger::Agent debugger_agent_; diff --git a/src/node.cc b/src/node.cc index b048166cd9d8df..ef59a103c30eac 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1132,6 +1132,8 @@ Local MakeCallback(Environment* env, bool ran_init_callback = false; bool has_domain = false; + Environment::AsyncCallbackScope callback_scope(env); + // TODO(trevnorris): Adding "_asyncQueue" to the "this" in the init callback // is a horrible way to detect usage. Rethink how detection should happen. if (recv->IsObject()) { @@ -1192,7 +1194,7 @@ Local MakeCallback(Environment* env, } } - if (!env->KickNextTick()) + if (!env->KickNextTick(&callback_scope)) return Undefined(env->isolate()); return ret; @@ -4100,7 +4102,10 @@ static void StartNodeInstance(void* arg) { if (instance_data->use_debug_agent()) StartDebug(env, debug_wait_connect); - LoadEnvironment(env); + { + Environment::AsyncCallbackScope callback_scope(env); + LoadEnvironment(env); + } env->set_trace_sync_io(trace_sync_io); diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 8c976b2e9f0b4c..c6254d57c840ed 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -579,6 +579,8 @@ class Parser : public BaseObject { if (!cb->IsFunction()) return; + Environment::AsyncCallbackScope callback_scope(parser->env()); + // Hooks for GetCurrentBuffer parser->current_buffer_len_ = nread; parser->current_buffer_data_ = buf->base; @@ -588,7 +590,7 @@ class Parser : public BaseObject { parser->current_buffer_len_ = 0; parser->current_buffer_data_ = nullptr; - parser->env()->KickNextTick(); + parser->env()->KickNextTick(&callback_scope); } diff --git a/src/node_internals.h b/src/node_internals.h index 3df7676d2a9d51..6e3f7204777835 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -69,8 +69,6 @@ v8::Local MakeCallback(Environment* env, int argc = 0, v8::Local* argv = nullptr); -bool KickNextTick(); - // Convert a struct sockaddr to a { address: '1.2.3.4', port: 1234 } JS object. // Sets address and port properties on the info object and returns it. // If |info| is omitted, a new object is returned. From 7d369f1fb63cac32fff69fa2d68beefb0a55a9bb Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Thu, 11 Feb 2016 13:10:47 -0700 Subject: [PATCH 03/18] src: remove unused of TickInfo::last_threw() Environment::TickInfo::last_threw() is no longer in use. Also pass Isolate to few methods and fix whitespace alignment. PR-URL: https://github.com/nodejs/node/pull/4507 Reviewed-By: Fedor Indutny --- src/async-wrap.cc | 5 ++--- src/env-inl.h | 10 +--------- src/env.cc | 3 +-- src/env.h | 3 --- src/node.cc | 10 +++++----- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 01dcaf277c1840..789e357c732773 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -173,8 +173,8 @@ void LoadAsyncWrapperInfo(Environment* env) { Local AsyncWrap::MakeCallback(const Local cb, - int argc, - Local* argv) { + int argc, + Local* argv) { CHECK(env()->context() == env()->isolate()->GetCurrentContext()); Local pre_fn = env()->async_hooks_pre_function(); @@ -254,7 +254,6 @@ Local AsyncWrap::MakeCallback(const Local cb, env()->tick_callback_function()->Call(process, 0, nullptr); if (try_catch.HasCaught()) { - tick_info->set_last_threw(true); return Undefined(env()->isolate()); } diff --git a/src/env-inl.h b/src/env-inl.h index eebb68eb463e93..f50ec13732e015 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -118,7 +118,7 @@ inline uint32_t Environment::DomainFlag::count() const { return fields_[kCount]; } -inline Environment::TickInfo::TickInfo() : in_tick_(false), last_threw_(false) { +inline Environment::TickInfo::TickInfo() : in_tick_(false) { for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; } @@ -139,10 +139,6 @@ inline uint32_t Environment::TickInfo::index() const { return fields_[kIndex]; } -inline bool Environment::TickInfo::last_threw() const { - return last_threw_; -} - inline uint32_t Environment::TickInfo::length() const { return fields_[kLength]; } @@ -155,10 +151,6 @@ inline void Environment::TickInfo::set_index(uint32_t value) { fields_[kIndex] = value; } -inline void Environment::TickInfo::set_last_threw(bool value) { - last_threw_ = value; -} - inline Environment::ArrayBufferAllocatorInfo::ArrayBufferAllocatorInfo() { for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; diff --git a/src/env.cc b/src/env.cc index 8fa6e5c43afe06..a9b108c47a49be 100644 --- a/src/env.cc +++ b/src/env.cc @@ -74,12 +74,11 @@ bool Environment::KickNextTick(Environment::AsyncCallbackScope* scope) { } // process nextTicks after call - TryCatch try_catch; + TryCatch try_catch(isolate()); try_catch.SetVerbose(true); tick_callback_function()->Call(process_object(), 0, nullptr); if (try_catch.HasCaught()) { - info->set_last_threw(true); return false; } diff --git a/src/env.h b/src/env.h index 8ae681ff43c86f..5849d7a92107e2 100644 --- a/src/env.h +++ b/src/env.h @@ -332,12 +332,10 @@ class Environment { inline uint32_t* fields(); inline int fields_count() const; inline bool in_tick() const; - inline bool last_threw() const; inline uint32_t index() const; inline uint32_t length() const; inline void set_in_tick(bool value); inline void set_index(uint32_t value); - inline void set_last_threw(bool value); private: friend class Environment; // So we can call the constructor. @@ -351,7 +349,6 @@ class Environment { uint32_t fields_[kFieldsCount]; bool in_tick_; - bool last_threw_; DISALLOW_COPY_AND_ASSIGN(TickInfo); }; diff --git a/src/node.cc b/src/node.cc index ef59a103c30eac..fc39b8399a1ffa 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1119,10 +1119,10 @@ void SetupPromises(const FunctionCallbackInfo& args) { Local MakeCallback(Environment* env, - Local recv, - const Local callback, - int argc, - Local argv[]) { + Local recv, + const Local callback, + int argc, + Local argv[]) { // If you hit this assertion, you forgot to enter the v8::Context first. CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); @@ -1154,7 +1154,7 @@ Local MakeCallback(Environment* env, } } - TryCatch try_catch; + TryCatch try_catch(env->isolate()); try_catch.SetVerbose(true); if (has_domain) { From 0da31a1e1eb44b0601953d140b770e433796bdd9 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 10 Feb 2016 12:48:44 -0700 Subject: [PATCH 04/18] src: remove unused TickInfo::in_tick() PR-URL: https://github.com/nodejs/node/pull/4507 Reviewed-By: Fedor Indutny --- src/env-inl.h | 10 +--------- src/env.h | 3 --- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/env-inl.h b/src/env-inl.h index f50ec13732e015..9084cb5159e58a 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -118,7 +118,7 @@ inline uint32_t Environment::DomainFlag::count() const { return fields_[kCount]; } -inline Environment::TickInfo::TickInfo() : in_tick_(false) { +inline Environment::TickInfo::TickInfo() { for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; } @@ -131,10 +131,6 @@ inline int Environment::TickInfo::fields_count() const { return kFieldsCount; } -inline bool Environment::TickInfo::in_tick() const { - return in_tick_; -} - inline uint32_t Environment::TickInfo::index() const { return fields_[kIndex]; } @@ -143,10 +139,6 @@ inline uint32_t Environment::TickInfo::length() const { return fields_[kLength]; } -inline void Environment::TickInfo::set_in_tick(bool value) { - in_tick_ = value; -} - inline void Environment::TickInfo::set_index(uint32_t value) { fields_[kIndex] = value; } diff --git a/src/env.h b/src/env.h index 5849d7a92107e2..44318da1fa3d0f 100644 --- a/src/env.h +++ b/src/env.h @@ -331,10 +331,8 @@ class Environment { public: inline uint32_t* fields(); inline int fields_count() const; - inline bool in_tick() const; inline uint32_t index() const; inline uint32_t length() const; - inline void set_in_tick(bool value); inline void set_index(uint32_t value); private: @@ -348,7 +346,6 @@ class Environment { }; uint32_t fields_[kFieldsCount]; - bool in_tick_; DISALLOW_COPY_AND_ASSIGN(TickInfo); }; From a6a738286036ae9d6ee9f61dd528df453da7fc09 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Thu, 11 Feb 2016 13:57:26 -0700 Subject: [PATCH 05/18] src: remove TryCatch in MakeCallback 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. PR-URL: https://github.com/nodejs/node/pull/4507 Reviewed-By: Fedor Indutny --- src/async-wrap.cc | 28 +++++++++++----------------- src/env.cc | 13 ++++--------- src/node.cc | 27 ++++++++++++--------------- 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 789e357c732773..a7a9822238329a 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -196,33 +196,28 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - TryCatch try_catch(env()->isolate()); - try_catch.SetVerbose(true); - if (has_domain) { Local enter_v = domain->Get(env()->enter_string()); if (enter_v->IsFunction()) { - enter_v.As()->Call(domain, 0, nullptr); - if (try_catch.HasCaught()) - return Undefined(env()->isolate()); + if (enter_v.As()->Call(domain, 0, nullptr).IsEmpty()) { + FatalError("node::AsyncWrap::MakeCallback", + "domain enter callback threw, please report this"); + } } } if (ran_init_callback() && !pre_fn.IsEmpty()) { - 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"); } Local ret = cb->Call(context, argc, argv); if (ran_init_callback() && !post_fn.IsEmpty()) { - 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"); } - // If the return value is empty then the callback threw. if (ret.IsEmpty()) { return Undefined(env()->isolate()); } @@ -230,9 +225,10 @@ Local AsyncWrap::MakeCallback(const Local cb, if (has_domain) { Local exit_v = domain->Get(env()->exit_string()); if (exit_v->IsFunction()) { - exit_v.As()->Call(domain, 0, nullptr); - if (try_catch.HasCaught()) - return Undefined(env()->isolate()); + if (exit_v.As()->Call(domain, 0, nullptr).IsEmpty()) { + FatalError("node::AsyncWrap::MakeCallback", + "domain exit callback threw, please report this"); + } } } @@ -251,9 +247,7 @@ Local AsyncWrap::MakeCallback(const Local cb, return ret; } - env()->tick_callback_function()->Call(process, 0, nullptr); - - if (try_catch.HasCaught()) { + if (env()->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { return Undefined(env()->isolate()); } diff --git a/src/env.cc b/src/env.cc index a9b108c47a49be..144a37f2e3cb67 100644 --- a/src/env.cc +++ b/src/env.cc @@ -11,6 +11,7 @@ using v8::Message; using v8::StackFrame; using v8::StackTrace; using v8::TryCatch; +using v8::Value; void Environment::PrintSyncTrace() const { if (!trace_sync_io_) @@ -73,16 +74,10 @@ bool Environment::KickNextTick(Environment::AsyncCallbackScope* scope) { return true; } - // process nextTicks after call - TryCatch try_catch(isolate()); - try_catch.SetVerbose(true); - tick_callback_function()->Call(process_object(), 0, nullptr); + Local ret = + tick_callback_function()->Call(process_object(), 0, nullptr); - if (try_catch.HasCaught()) { - return false; - } - - return true; + return !ret.IsEmpty(); } } // namespace node diff --git a/src/node.cc b/src/node.cc index fc39b8399a1ffa..5b4b2f8c8affa0 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1154,33 +1154,28 @@ Local MakeCallback(Environment* env, } } - TryCatch try_catch(env->isolate()); - try_catch.SetVerbose(true); - if (has_domain) { Local enter_v = domain->Get(env->enter_string()); if (enter_v->IsFunction()) { - enter_v.As()->Call(domain, 0, nullptr); - if (try_catch.HasCaught()) - return Undefined(env->isolate()); + if (enter_v.As()->Call(domain, 0, nullptr).IsEmpty()) { + FatalError("node::MakeCallback", + "domain enter callback threw, please report this"); + } } } if (ran_init_callback && !pre_fn.IsEmpty()) { - pre_fn->Call(object, 0, nullptr); - if (try_catch.HasCaught()) + if (pre_fn->Call(object, 0, nullptr).IsEmpty()) FatalError("node::MakeCallback", "pre hook threw"); } Local ret = callback->Call(recv, argc, argv); if (ran_init_callback && !post_fn.IsEmpty()) { - post_fn->Call(object, 0, nullptr); - if (try_catch.HasCaught()) + if (post_fn->Call(object, 0, nullptr).IsEmpty()) FatalError("node::MakeCallback", "post hook threw"); } - // If the return value is empty then the callback threw. if (ret.IsEmpty()) { return Undefined(env->isolate()); } @@ -1188,14 +1183,16 @@ Local MakeCallback(Environment* env, if (has_domain) { Local exit_v = domain->Get(env->exit_string()); if (exit_v->IsFunction()) { - exit_v.As()->Call(domain, 0, nullptr); - if (try_catch.HasCaught()) - return Undefined(env->isolate()); + if (exit_v.As()->Call(domain, 0, nullptr).IsEmpty()) { + FatalError("node::MakeCallback", + "domain exit callback threw, please report this"); + } } } - if (!env->KickNextTick(&callback_scope)) + if (!env->KickNextTick(&callback_scope)) { return Undefined(env->isolate()); + } return ret; } From 1adb057b5b92f4ea7a99c7287f2acf6c8165e66f Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 6 Jan 2016 10:39:40 -0700 Subject: [PATCH 06/18] test: add addons test for MakeCallback 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 PR-URL: https://github.com/nodejs/node/pull/4507 Reviewed-By: Fedor Indutny --- test/addons/make-callback-recurse/binding.cc | 31 ++++ test/addons/make-callback-recurse/binding.gyp | 8 + test/addons/make-callback-recurse/test.js | 170 ++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 test/addons/make-callback-recurse/binding.cc create mode 100644 test/addons/make-callback-recurse/binding.gyp create mode 100644 test/addons/make-callback-recurse/test.js diff --git a/test/addons/make-callback-recurse/binding.cc b/test/addons/make-callback-recurse/binding.cc new file mode 100644 index 00000000000000..1c575910ef66f5 --- /dev/null +++ b/test/addons/make-callback-recurse/binding.cc @@ -0,0 +1,31 @@ +#include "node.h" +#include "v8.h" + +#include "../../../src/util.h" + +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::Value; + +namespace { + +void MakeCallback(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsObject()); + CHECK(args[1]->IsFunction()); + Isolate* isolate = args.GetIsolate(); + Local recv = args[0].As(); + Local method = args[1].As(); + + node::MakeCallback(isolate, recv, method, 0, nullptr); +} + +void Initialize(Local target) { + NODE_SET_METHOD(target, "makeCallback", MakeCallback); +} + +} // namespace anonymous + +NODE_MODULE(binding, Initialize) diff --git a/test/addons/make-callback-recurse/binding.gyp b/test/addons/make-callback-recurse/binding.gyp new file mode 100644 index 00000000000000..3bfb84493f3e87 --- /dev/null +++ b/test/addons/make-callback-recurse/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/make-callback-recurse/test.js b/test/addons/make-callback-recurse/test.js new file mode 100644 index 00000000000000..f924ac61916ba5 --- /dev/null +++ b/test/addons/make-callback-recurse/test.js @@ -0,0 +1,170 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const domain = require('domain'); +const binding = require('./build/Release/binding'); +const makeCallback = binding.makeCallback; + +// Make sure this is run in the future. +const mustCallCheckDomains = common.mustCall(checkDomains); + + +// Make sure that using MakeCallback allows the error to propagate. +assert.throws(function() { + makeCallback({}, function() { + throw new Error('hi from domain error'); + }); +}); + + +// Check the execution order of the nextTickQueue and MicrotaskQueue in +// relation to running multiple MakeCallback's from bootstrap, +// node::MakeCallback() and node::AsyncWrap::MakeCallback(). +// TODO(trevnorris): Is there a way to verify this is being run during +// bootstrap? +(function verifyExecutionOrder(arg) { + const results_arr = []; + + // Processing of the MicrotaskQueue is manually handled by node. They are not + // processed until after the nextTickQueue has been processed. + Promise.resolve(1).then(common.mustCall(function() { + results_arr.push(7); + })); + + // The nextTick should run after all immediately invoked calls. + process.nextTick(common.mustCall(function() { + results_arr.push(3); + + // Run same test again but while processing the nextTickQueue to make sure + // the following MakeCallback call breaks in the middle of processing the + // queue and allows the script to run normally. + process.nextTick(common.mustCall(function() { + results_arr.push(6); + })); + + makeCallback({}, common.mustCall(function() { + results_arr.push(4); + })); + + results_arr.push(5); + })); + + results_arr.push(0); + + // MakeCallback is calling the function immediately, but should then detect + // that a script is already in the middle of execution and return before + // either the nextTickQueue or MicrotaskQueue are processed. + makeCallback({}, common.mustCall(function() { + results_arr.push(1); + })); + + // This should run before either the nextTickQueue or MicrotaskQueue are + // processed. Previously MakeCallback would not detect this circumstance + // and process them immediately. + results_arr.push(2); + + setImmediate(common.mustCall(function() { + for (var i = 0; i < results_arr.length; i++) { + assert.equal(results_arr[i], + i, + `verifyExecutionOrder(${arg}) results: ${results_arr}`); + } + if (arg === 1) { + // The tests are first run on bootstrap during LoadEnvironment() in + // src/node.cc. Now run the tests through node::MakeCallback(). + setImmediate(function() { + makeCallback({}, common.mustCall(function() { + verifyExecutionOrder(2); + })); + }); + } else if (arg === 2) { + // setTimeout runs via the TimerWrap, which runs through + // AsyncWrap::MakeCallback(). Make sure there are no conflicts using + // node::MakeCallback() within it. + setTimeout(common.mustCall(function() { + verifyExecutionOrder(3); + }), 10); + } else if (arg === 3) { + mustCallCheckDomains(); + } else { + throw new Error('UNREACHABLE'); + } + })); +}(1)); + + +function checkDomains() { + // Check that domains are properly entered/exited when called in multiple + // levels from both node::MakeCallback() and AsyncWrap::MakeCallback + setImmediate(common.mustCall(function() { + const d1 = domain.create(); + const d2 = domain.create(); + const d3 = domain.create(); + + makeCallback({ domain: d1 }, common.mustCall(function() { + assert.equal(d1, process.domain); + makeCallback({ domain: d2 }, common.mustCall(function() { + assert.equal(d2, process.domain); + makeCallback({ domain: d3 }, common.mustCall(function() { + assert.equal(d3, process.domain); + })); + assert.equal(d2, process.domain); + })); + assert.equal(d1, process.domain); + })); + })); + + setTimeout(common.mustCall(function() { + const d1 = domain.create(); + const d2 = domain.create(); + const d3 = domain.create(); + + makeCallback({ domain: d1 }, common.mustCall(function() { + assert.equal(d1, process.domain); + makeCallback({ domain: d2 }, common.mustCall(function() { + assert.equal(d2, process.domain); + makeCallback({ domain: d3 }, common.mustCall(function() { + assert.equal(d3, process.domain); + })); + assert.equal(d2, process.domain); + })); + assert.equal(d1, process.domain); + })); + }), 1); + + // Make sure nextTick, setImmediate and setTimeout can all recover properly + // after a thrown makeCallback call. + process.nextTick(common.mustCall(function() { + const d = domain.create(); + d.on('error', common.mustCall(function(e) { + assert.equal(e.message, 'throw from domain 3'); + })); + makeCallback({ domain: d }, function() { + throw new Error('throw from domain 3'); + }); + throw new Error('UNREACHABLE'); + })); + + setImmediate(common.mustCall(function() { + const d = domain.create(); + d.on('error', common.mustCall(function(e) { + assert.equal(e.message, 'throw from domain 2'); + })); + makeCallback({ domain: d }, function() { + throw new Error('throw from domain 2'); + }); + throw new Error('UNREACHABLE'); + })); + + setTimeout(common.mustCall(function() { + const d = domain.create(); + d.on('error', common.mustCall(function(e) { + assert.equal(e.message, 'throw from domain 1'); + })); + makeCallback({ domain: d }, function() { + throw new Error('throw from domain 1'); + }); + throw new Error('UNREACHABLE'); + })); +} From a321e6d44e0377933f08326ceaae1aca4cf06366 Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 15 Feb 2016 04:45:20 -0500 Subject: [PATCH 07/18] src: remove unnecessary check The value's type is unsigned so it will always be >= 0. PR-URL: https://github.com/nodejs/node/pull/5233 Reviewed-By: Ben Noordhuis Reviewed-By: James M Snell --- src/env-inl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/env-inl.h b/src/env-inl.h index 9084cb5159e58a..6f19ff50cb536f 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -95,7 +95,6 @@ inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env) inline Environment::AsyncCallbackScope::~AsyncCallbackScope() { env_->makecallback_cntr_--; - CHECK_GE(env_->makecallback_cntr_, 0); } inline bool Environment::AsyncCallbackScope::in_makecallback() { From 331b85c39bd835c65ce49e32dd1b7bec334a702c Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Sat, 9 Jan 2016 10:58:55 +0100 Subject: [PATCH 08/18] async_wrap: add uid to all asyncWrap hooks By doing this users can use a Map object for storing information instead of modifying the handle object. PR-URL: #4600 Reviewed-By: Trevor Norris Reviewed-By: Sakthipriyan Vairamani --- src/async-wrap.cc | 5 ++- test/parallel/test-async-wrap-uid.js | 57 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-async-wrap-uid.js diff --git a/src/async-wrap.cc b/src/async-wrap.cc index a7a9822238329a..11a696cc2329a3 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -179,6 +179,7 @@ Local AsyncWrap::MakeCallback(const Local cb, Local pre_fn = env()->async_hooks_pre_function(); Local post_fn = env()->async_hooks_post_function(); + Local uid = Integer::New(env()->isolate(), get_uid()); Local context = object(); Local process = env()->process_object(); Local domain; @@ -207,14 +208,14 @@ Local AsyncWrap::MakeCallback(const Local cb, } if (ran_init_callback() && !pre_fn.IsEmpty()) { - if (pre_fn->Call(context, 0, nullptr).IsEmpty()) + if (pre_fn->Call(context, 1, &uid).IsEmpty()) FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); } Local ret = cb->Call(context, argc, argv); if (ran_init_callback() && !post_fn.IsEmpty()) { - if (post_fn->Call(context, 0, nullptr).IsEmpty()) + if (post_fn->Call(context, 1, &uid).IsEmpty()) FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); } diff --git a/test/parallel/test-async-wrap-uid.js b/test/parallel/test-async-wrap-uid.js new file mode 100644 index 00000000000000..ad2dd5027cfa1d --- /dev/null +++ b/test/parallel/test-async-wrap-uid.js @@ -0,0 +1,57 @@ +'use strict'; + +require('../common'); +const fs = require('fs'); +const assert = require('assert'); +const async_wrap = process.binding('async_wrap'); + +const storage = new Map(); +async_wrap.setupHooks(init, pre, post, destroy); +async_wrap.enable(); + +function init(provider, uid) { + storage.set(uid, { + init: true, + pre: false, + post: false, + destroy: false + }); +} + +function pre(uid) { + storage.get(uid).pre = true; +} + +function post(uid) { + storage.get(uid).post = true; +} + +function destroy(uid) { + storage.get(uid).destroy = true; +} + +fs.access(__filename, function(err) { + assert.ifError(err); +}); + +fs.access(__filename, function(err) { + assert.ifError(err); +}); + +async_wrap.disable(); + +process.once('exit', function() { + assert.strictEqual(storage.size, 2); + + for (const item of storage) { + const uid = item[0]; + const value = item[1]; + assert.strictEqual(typeof uid, 'number'); + assert.deepStrictEqual(value, { + init: true, + pre: true, + post: true, + destroy: true + }); + } +}); From 1c9cd4a3a6d10c6dd91c5af1264c2647221e3ad1 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Sat, 9 Jan 2016 12:27:53 +0100 Subject: [PATCH 09/18] async_wrap: make uid the first argument in init All other hooks have uid as the first argument, this makes it concistent for all hooks. PR-URL: #4600 Reviewed-By: Trevor Norris Reviewed-By: Sakthipriyan Vairamani --- src/async-wrap-inl.h | 2 +- test/parallel/test-async-wrap-check-providers.js | 4 ++-- test/parallel/test-async-wrap-disabled-propagate-parent.js | 2 +- test/parallel/test-async-wrap-propagate-parent.js | 2 +- test/parallel/test-async-wrap-uid.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index f2d2c3ecf1c7b4..d1197edc05f20c 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -40,8 +40,8 @@ inline AsyncWrap::AsyncWrap(Environment* env, v8::HandleScope scope(env->isolate()); v8::Local argv[] = { - v8::Int32::New(env->isolate(), provider), v8::Integer::New(env->isolate(), get_uid()), + v8::Int32::New(env->isolate(), provider), Null(env->isolate()) }; diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 907b9fa3159926..c08c7a43d445e0 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -29,8 +29,8 @@ if (common.isAix) { } } -function init(id) { - keyList = keyList.filter((e) => e != pkeys[id]); +function init(id, provider) { + keyList = keyList.filter((e) => e != pkeys[provider]); } function noop() { } diff --git a/test/parallel/test-async-wrap-disabled-propagate-parent.js b/test/parallel/test-async-wrap-disabled-propagate-parent.js index 55d2e59efb9ee7..f7badaffdffcb7 100644 --- a/test/parallel/test-async-wrap-disabled-propagate-parent.js +++ b/test/parallel/test-async-wrap-disabled-propagate-parent.js @@ -9,7 +9,7 @@ const providers = Object.keys(async_wrap.Providers); let cntr = 0; let client; -function init(type, id, parent) { +function init(id, type, parent) { if (parent) { cntr++; // Cannot assert in init callback or will abort. diff --git a/test/parallel/test-async-wrap-propagate-parent.js b/test/parallel/test-async-wrap-propagate-parent.js index 6001539c624678..31aa814100069b 100644 --- a/test/parallel/test-async-wrap-propagate-parent.js +++ b/test/parallel/test-async-wrap-propagate-parent.js @@ -8,7 +8,7 @@ const async_wrap = process.binding('async_wrap'); let cntr = 0; let client; -function init(type, id, parent) { +function init(id, type, parent) { if (parent) { cntr++; // Cannot assert in init callback or will abort. diff --git a/test/parallel/test-async-wrap-uid.js b/test/parallel/test-async-wrap-uid.js index ad2dd5027cfa1d..4e664f1bd46ebb 100644 --- a/test/parallel/test-async-wrap-uid.js +++ b/test/parallel/test-async-wrap-uid.js @@ -9,7 +9,7 @@ const storage = new Map(); async_wrap.setupHooks(init, pre, post, destroy); async_wrap.enable(); -function init(provider, uid) { +function init(uid) { storage.set(uid, { init: true, pre: false, From 051103df35be0fcba774133ac1dc9f79b2ba8b40 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 20 Jan 2016 18:20:43 +0100 Subject: [PATCH 10/18] async_wrap: add parent uid to init hook When the parent uid is required it is not necessary to store the uid in the parent handle object. PR-URL: #4600 Reviewed-By: Trevor Norris Reviewed-By: Sakthipriyan Vairamani --- src/async-wrap-inl.h | 7 +++++-- .../test-async-wrap-disabled-propagate-parent.js | 13 ++++++++++--- test/parallel/test-async-wrap-propagate-parent.js | 15 ++++++++++++--- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index d1197edc05f20c..eeea5973cac57b 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -42,11 +42,14 @@ inline AsyncWrap::AsyncWrap(Environment* env, v8::Local argv[] = { v8::Integer::New(env->isolate(), get_uid()), v8::Int32::New(env->isolate(), provider), + Null(env->isolate()), Null(env->isolate()) }; - if (parent != nullptr) - argv[2] = parent->object(); + if (parent != nullptr) { + argv[2] = v8::Integer::New(env->isolate(), parent->get_uid()); + argv[3] = parent->object(); + } v8::MaybeLocal ret = init_fn->Call(env->context(), object, arraysize(argv), argv); diff --git a/test/parallel/test-async-wrap-disabled-propagate-parent.js b/test/parallel/test-async-wrap-disabled-propagate-parent.js index f7badaffdffcb7..152af4bcde80fc 100644 --- a/test/parallel/test-async-wrap-disabled-propagate-parent.js +++ b/test/parallel/test-async-wrap-disabled-propagate-parent.js @@ -6,16 +6,23 @@ const net = require('net'); const async_wrap = process.binding('async_wrap'); const providers = Object.keys(async_wrap.Providers); +const uidSymbol = Symbol('uid'); + let cntr = 0; let client; -function init(id, type, parent) { - if (parent) { +function init(uid, type, parentUid, parentHandle) { + this[uidSymbol] = uid; + + if (parentHandle) { cntr++; // Cannot assert in init callback or will abort. process.nextTick(() => { assert.equal(providers[type], 'TCPWRAP'); - assert.equal(parent, server._handle, 'server doesn\'t match parent'); + assert.equal(parentUid, server._handle[uidSymbol], + 'server uid doesn\'t match parent uid'); + assert.equal(parentHandle, server._handle, + 'server handle doesn\'t match parent handle'); assert.equal(this, client._handle, 'client doesn\'t match context'); }); } diff --git a/test/parallel/test-async-wrap-propagate-parent.js b/test/parallel/test-async-wrap-propagate-parent.js index 31aa814100069b..e6cb5fa77bdaf7 100644 --- a/test/parallel/test-async-wrap-propagate-parent.js +++ b/test/parallel/test-async-wrap-propagate-parent.js @@ -4,16 +4,25 @@ const common = require('../common'); const assert = require('assert'); const net = require('net'); const async_wrap = process.binding('async_wrap'); +const providers = Object.keys(async_wrap.Providers); + +const uidSymbol = Symbol('uid'); let cntr = 0; let client; -function init(id, type, parent) { - if (parent) { +function init(uid, type, parentUid, parentHandle) { + this[uidSymbol] = uid; + + if (parentHandle) { cntr++; // Cannot assert in init callback or will abort. process.nextTick(() => { - assert.equal(parent, server._handle, 'server doesn\'t match parent'); + assert.equal(providers[type], 'TCPWRAP'); + assert.equal(parentUid, server._handle[uidSymbol], + 'server uid doesn\'t match parent uid'); + assert.equal(parentHandle, server._handle, + 'server handle doesn\'t match parent handle'); assert.equal(this, client._handle, 'client doesn\'t match context'); }); } From 8f4438d8c3f25c19489f44f2f987951b8e5cc783 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Fri, 19 Feb 2016 13:13:04 -0700 Subject: [PATCH 11/18] http_parser: use `MakeCallback` Make `HTTPParser` an instance of `AsyncWrap` and make it use `MakeCallback`. This means that async wrap hooks will be called on consumed TCP sockets as well as on non-consumed ones. Additional uses of `AsyncCallbackScope` are necessary to prevent improper state from progressing that triggers failure in the test-http-pipeline-flood.js test. Optimally this wouldn't be necessary, but for the time being it's the most sure way to allow operations to proceed as they have. Fix: https://github.com/nodejs/node/issues/4416 PR-URL: https://github.com/nodejs/node/pull/5419 Reviewed-By: Fedor Indutny --- src/async-wrap.h | 1 + src/node_http_parser.cc | 27 ++++++++++++------- .../test-async-wrap-check-providers.js | 3 +++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/async-wrap.h b/src/async-wrap.h index 5db29600bcd180..cb0c9e211a8923 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -17,6 +17,7 @@ namespace node { V(FSREQWRAP) \ V(GETADDRINFOREQWRAP) \ V(GETNAMEINFOREQWRAP) \ + V(HTTPPARSER) \ V(JSSTREAM) \ V(PIPEWRAP) \ V(PIPECONNECTWRAP) \ diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index c6254d57c840ed..87126bfb33a497 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -3,8 +3,8 @@ #include "node_http_parser.h" #include "node_revert.h" -#include "base-object.h" -#include "base-object-inl.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "stream_base.h" @@ -148,10 +148,10 @@ struct StringPtr { }; -class Parser : public BaseObject { +class Parser : public AsyncWrap { public: Parser(Environment* env, Local wrap, enum http_parser_type type) - : BaseObject(env, wrap), + : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER), current_buffer_len_(0), current_buffer_data_(nullptr) { Wrap(object(), this); @@ -165,6 +165,11 @@ class Parser : public BaseObject { } + size_t self_size() const override { + return sizeof(*this); + } + + HTTP_CB(on_message_begin) { num_fields_ = num_values_ = 0; url_.Reset(); @@ -286,8 +291,10 @@ class Parser : public BaseObject { argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade); + Environment::AsyncCallbackScope callback_scope(env()); + Local head_response = - cb.As()->Call(obj, arraysize(argv), argv); + MakeCallback(cb.As(), arraysize(argv), argv); if (head_response.IsEmpty()) { got_exception_ = true; @@ -322,7 +329,7 @@ class Parser : public BaseObject { Integer::NewFromUnsigned(env()->isolate(), length) }; - Local r = cb.As()->Call(obj, arraysize(argv), argv); + Local r = MakeCallback(cb.As(), arraysize(argv), argv); if (r.IsEmpty()) { got_exception_ = true; @@ -345,7 +352,9 @@ class Parser : public BaseObject { if (!cb->IsFunction()) return 0; - Local r = cb.As()->Call(obj, 0, nullptr); + Environment::AsyncCallbackScope callback_scope(env()); + + Local r = MakeCallback(cb.As(), 0, nullptr); if (r.IsEmpty()) { got_exception_ = true; @@ -585,7 +594,7 @@ class Parser : public BaseObject { parser->current_buffer_len_ = nread; parser->current_buffer_data_ = buf->base; - cb.As()->Call(obj, 1, &ret); + parser->MakeCallback(cb.As(), 1, &ret); parser->current_buffer_len_ = 0; parser->current_buffer_data_ = nullptr; @@ -659,7 +668,7 @@ class Parser : public BaseObject { url_.ToString(env()) }; - Local r = cb.As()->Call(obj, arraysize(argv), argv); + Local r = MakeCallback(cb.As(), arraysize(argv), argv); if (r.IsEmpty()) got_exception_ = true; diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index c08c7a43d445e0..3a2c80d9cbe173 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -11,6 +11,7 @@ const tls = require('tls'); const zlib = require('zlib'); const ChildProcess = require('child_process').ChildProcess; const StreamWrap = require('_stream_wrap').StreamWrap; +const HTTPParser = process.binding('http_parser').HTTPParser; const async_wrap = process.binding('async_wrap'); const pkeys = Object.keys(async_wrap.Providers); @@ -106,6 +107,8 @@ zlib.createGzip(); new ChildProcess(); +new HTTPParser(HTTPParser.REQUEST); + process.on('exit', function() { if (keyList.length !== 0) { process._rawDebug('Not all keys have been used:'); From 71d786271c4216b7eb0011d548414562645f2d9c Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Fri, 4 Mar 2016 15:14:27 -0700 Subject: [PATCH 12/18] src,http: fix uncaughtException miss in http In AsyncWrap::MakeCallback always return empty handle if there is an error. In the future this should change to return a v8::MaybeLocal, but that major change will have to wait for v6.x, and these changes are meant to be backported to v4.x. The HTTParser call to AsyncWrap::MakeCallback failed because it expected a thrown call to return an empty handle. In node::MakeCallback return an empty handle if the call is in_makecallback(), otherwise return v8::Undefined() as usual to preserve backwards compatibility. Fixes: https://github.com/nodejs/node/issues/5555 PR-URL: https://github.com/nodejs/node/pull/5591 Reviewed-By: Julien Gilli --- src/async-wrap.cc | 6 ++--- src/node.cc | 6 ++++- .../test-http-catch-uncaughtexception.js | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 test/parallel/test-http-catch-uncaughtexception.js diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 11a696cc2329a3..e2054031014680 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -193,7 +193,7 @@ Local AsyncWrap::MakeCallback(const Local cb, if (has_domain) { domain = domain_v.As(); if (domain->Get(env()->disposed_string())->IsTrue()) - return Undefined(env()->isolate()); + return Local(); } } @@ -220,7 +220,7 @@ Local AsyncWrap::MakeCallback(const Local cb, } if (ret.IsEmpty()) { - return Undefined(env()->isolate()); + return ret; } if (has_domain) { @@ -249,7 +249,7 @@ Local AsyncWrap::MakeCallback(const Local cb, } if (env()->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { - return Undefined(env()->isolate()); + return Local(); } return ret; diff --git a/src/node.cc b/src/node.cc index 5b4b2f8c8affa0..056b3711f0c8da 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1177,7 +1177,11 @@ Local MakeCallback(Environment* env, } if (ret.IsEmpty()) { - return Undefined(env->isolate()); + if (callback_scope.in_makecallback()) + return ret; + // NOTE: Undefined() is returned here for backwards compatibility. + else + return Undefined(env->isolate()); } if (has_domain) { diff --git a/test/parallel/test-http-catch-uncaughtexception.js b/test/parallel/test-http-catch-uncaughtexception.js new file mode 100644 index 00000000000000..c4054aafbfb699 --- /dev/null +++ b/test/parallel/test-http-catch-uncaughtexception.js @@ -0,0 +1,23 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +const uncaughtCallback = common.mustCall(function(er) { + assert.equal(er.message, 'get did fail'); +}); + +process.on('uncaughtException', uncaughtCallback); + +const server = http.createServer(function(req, res) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('bye'); +}).listen(common.PORT, function() { + http.get({ port: common.PORT }, function(res) { + res.resume(); + throw new Error('get did fail'); + }).on('close', function() { + server.close(); + }); +}); From d94eec1617eca6028555961efcc2ab403fdf6185 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Fri, 11 Mar 2016 12:55:59 -0700 Subject: [PATCH 13/18] src,http_parser: remove KickNextTick call Now that HTTPParser uses MakeCallback it is unnecessary to manually process the nextTickQueue. The KickNextTick function is now no longer needed so code has moved back to node::MakeCallback to simplify implementation. Include minor cleanup moving Environment::tick_info() call below the early return to save an operation. PR-URL: https://github.com/nodejs/node/pull/5756 Reviewed-By: Ben Noordhuis Reviewed-By: Andreas Madsen --- src/async-wrap.cc | 7 ++++--- src/env.cc | 23 ----------------------- src/env.h | 2 -- src/node.cc | 18 +++++++++++++++++- src/node_http_parser.cc | 4 ---- 5 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/async-wrap.cc b/src/async-wrap.cc index e2054031014680..dde07aa0756a3e 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -181,7 +181,6 @@ Local AsyncWrap::MakeCallback(const Local cb, Local post_fn = env()->async_hooks_post_function(); Local uid = Integer::New(env()->isolate(), get_uid()); Local context = object(); - Local process = env()->process_object(); Local domain; bool has_domain = false; @@ -233,16 +232,18 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - Environment::TickInfo* tick_info = env()->tick_info(); - if (callback_scope.in_makecallback()) { return ret; } + Environment::TickInfo* tick_info = env()->tick_info(); + if (tick_info->length() == 0) { env()->isolate()->RunMicrotasks(); } + Local process = env()->process_object(); + if (tick_info->length() == 0) { tick_info->set_index(0); return ret; diff --git a/src/env.cc b/src/env.cc index 144a37f2e3cb67..2509a8fd44b6a2 100644 --- a/src/env.cc +++ b/src/env.cc @@ -57,27 +57,4 @@ void Environment::PrintSyncTrace() const { fflush(stderr); } - -bool Environment::KickNextTick(Environment::AsyncCallbackScope* scope) { - TickInfo* info = tick_info(); - - if (scope->in_makecallback()) { - return true; - } - - if (info->length() == 0) { - isolate()->RunMicrotasks(); - } - - if (info->length() == 0) { - info->set_index(0); - return true; - } - - Local ret = - tick_callback_function()->Call(process_object(), 0, nullptr); - - return !ret.IsEmpty(); -} - } // namespace node diff --git a/src/env.h b/src/env.h index 44318da1fa3d0f..7b6ffc8b87c8ee 100644 --- a/src/env.h +++ b/src/env.h @@ -453,8 +453,6 @@ class Environment { inline int64_t get_async_wrap_uid(); - bool KickNextTick(AsyncCallbackScope* scope); - inline uint32_t* heap_statistics_buffer() const; inline void set_heap_statistics_buffer(uint32_t* pointer); diff --git a/src/node.cc b/src/node.cc index 056b3711f0c8da..6b095a6f5816b6 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1194,7 +1194,23 @@ Local MakeCallback(Environment* env, } } - if (!env->KickNextTick(&callback_scope)) { + if (callback_scope.in_makecallback()) { + return ret; + } + + Environment::TickInfo* tick_info = env->tick_info(); + + if (tick_info->length() == 0) { + env->isolate()->RunMicrotasks(); + } + + Local process = env->process_object(); + + if (tick_info->length() == 0) { + tick_info->set_index(0); + } + + if (env->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { return Undefined(env->isolate()); } diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 87126bfb33a497..6b15aa8c72d531 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -588,8 +588,6 @@ class Parser : public AsyncWrap { if (!cb->IsFunction()) return; - Environment::AsyncCallbackScope callback_scope(parser->env()); - // Hooks for GetCurrentBuffer parser->current_buffer_len_ = nread; parser->current_buffer_data_ = buf->base; @@ -598,8 +596,6 @@ class Parser : public AsyncWrap { parser->current_buffer_len_ = 0; parser->current_buffer_data_ = nullptr; - - parser->env()->KickNextTick(&callback_scope); } From 780dd2d3d43f113bb82736bc1fcf2d446741a9a8 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 23 Mar 2016 17:02:04 -0600 Subject: [PATCH 14/18] src: reword command and add ternary Make comment clear that Undefined() is returned for legacy compatibility. This will change in the future as a semver-major change, but to be able to port this to previous releases it needs to stay as is. PR-URL: https://github.com/nodejs/node/pull/5756 Reviewed-By: Ben Noordhuis Reviewed-By: Andreas Madsen --- src/node.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/node.cc b/src/node.cc index 6b095a6f5816b6..074589542f59ba 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1177,11 +1177,10 @@ Local MakeCallback(Environment* env, } if (ret.IsEmpty()) { - if (callback_scope.in_makecallback()) - return ret; - // NOTE: Undefined() is returned here for backwards compatibility. - else - return Undefined(env->isolate()); + // NOTE: For backwards compatibility with public API we return Undefined() + // if the top level call threw. + return callback_scope.in_makecallback() ? + ret : Undefined(env->isolate()).As(); } if (has_domain) { From 182b9de523af9c1b687dc16f4f7a18909cb314e0 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Mon, 22 Feb 2016 16:34:35 -0700 Subject: [PATCH 15/18] async_wrap: setupHooks now accepts object The number of callbacks accepted to setupHooks was getting unwieldy. Instead change the implementation to accept an object with all callbacks PR-URL: https://github.com/nodejs/node/pull/5756 Reviewed-By: Ben Noordhuis Reviewed-By: Andreas Madsen --- src/async-wrap.cc | 35 ++++++++++++++----- .../test-async-wrap-check-providers.js | 2 +- ...st-async-wrap-disabled-propagate-parent.js | 2 +- .../test-async-wrap-propagate-parent.js | 2 +- .../parallel/test-async-wrap-throw-no-init.js | 4 +-- test/parallel/test-async-wrap-uid.js | 2 +- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/async-wrap.cc b/src/async-wrap.cc index dde07aa0756a3e..570ed2f165b2ba 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -121,18 +121,35 @@ static void SetupHooks(const FunctionCallbackInfo& args) { if (env->async_hooks()->callbacks_enabled()) return env->ThrowError("hooks should not be set while also enabled"); - - if (!args[0]->IsFunction()) + if (!args[0]->IsObject()) + return env->ThrowTypeError("first argument must be an object"); + + Local fn_obj = args[0].As(); + + Local init_v = fn_obj->Get( + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "init")).ToLocalChecked(); + Local pre_v = fn_obj->Get( + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "pre")).ToLocalChecked(); + Local post_v = fn_obj->Get( + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "post")).ToLocalChecked(); + Local destroy_v = fn_obj->Get( + env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "destroy")).ToLocalChecked(); + + if (!init_v->IsFunction()) return env->ThrowTypeError("init callback must be a function"); - env->set_async_hooks_init_function(args[0].As()); + env->set_async_hooks_init_function(init_v.As()); - if (args[1]->IsFunction()) - env->set_async_hooks_pre_function(args[1].As()); - if (args[2]->IsFunction()) - env->set_async_hooks_post_function(args[2].As()); - if (args[3]->IsFunction()) - env->set_async_hooks_destroy_function(args[3].As()); + if (pre_v->IsFunction()) + env->set_async_hooks_pre_function(pre_v.As()); + if (post_v->IsFunction()) + env->set_async_hooks_post_function(post_v.As()); + if (destroy_v->IsFunction()) + env->set_async_hooks_destroy_function(destroy_v.As()); } diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 3a2c80d9cbe173..5ae8654d337303 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -36,7 +36,7 @@ function init(id, provider) { function noop() { } -async_wrap.setupHooks(init, noop, noop); +async_wrap.setupHooks({ init }); async_wrap.enable(); diff --git a/test/parallel/test-async-wrap-disabled-propagate-parent.js b/test/parallel/test-async-wrap-disabled-propagate-parent.js index 152af4bcde80fc..f0daaeb06d0031 100644 --- a/test/parallel/test-async-wrap-disabled-propagate-parent.js +++ b/test/parallel/test-async-wrap-disabled-propagate-parent.js @@ -30,7 +30,7 @@ function init(uid, type, parentUid, parentHandle) { function noop() { } -async_wrap.setupHooks(init, noop, noop); +async_wrap.setupHooks({ init }); async_wrap.enable(); const server = net.createServer(function(c) { diff --git a/test/parallel/test-async-wrap-propagate-parent.js b/test/parallel/test-async-wrap-propagate-parent.js index e6cb5fa77bdaf7..34a00c22eeeafd 100644 --- a/test/parallel/test-async-wrap-propagate-parent.js +++ b/test/parallel/test-async-wrap-propagate-parent.js @@ -30,7 +30,7 @@ function init(uid, type, parentUid, parentHandle) { function noop() { } -async_wrap.setupHooks(init, noop, noop); +async_wrap.setupHooks({ init }); async_wrap.enable(); const server = net.createServer(function(c) { diff --git a/test/parallel/test-async-wrap-throw-no-init.js b/test/parallel/test-async-wrap-throw-no-init.js index 768e38e8eff389..ccf77f66dc7232 100644 --- a/test/parallel/test-async-wrap-throw-no-init.js +++ b/test/parallel/test-async-wrap-throw-no-init.js @@ -7,14 +7,14 @@ const async_wrap = process.binding('async_wrap'); assert.throws(function() { async_wrap.setupHooks(null); -}, /init callback must be a function/); +}, /first argument must be an object/); assert.throws(function() { async_wrap.enable(); }, /init callback is not assigned to a function/); // Should not throw -async_wrap.setupHooks(() => {}); +async_wrap.setupHooks({ init: () => {} }); async_wrap.enable(); assert.throws(function() { diff --git a/test/parallel/test-async-wrap-uid.js b/test/parallel/test-async-wrap-uid.js index 4e664f1bd46ebb..5bf3a8856e0e3f 100644 --- a/test/parallel/test-async-wrap-uid.js +++ b/test/parallel/test-async-wrap-uid.js @@ -6,7 +6,7 @@ const assert = require('assert'); const async_wrap = process.binding('async_wrap'); const storage = new Map(); -async_wrap.setupHooks(init, pre, post, destroy); +async_wrap.setupHooks({ init, pre, post, destroy }); async_wrap.enable(); function init(uid) { From 61dd47f9135b3b8d31bc0eb3ff7900adddb0217a Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Mon, 22 Feb 2016 22:50:56 -0700 Subject: [PATCH 16/18] async_wrap: notify post if intercepted exception The second argument of the post callback is a boolean indicating whether the callback threw and was intercepted by uncaughtException or a domain. Currently node::MakeCallback has no way of retrieving a uid for the object. This is coming in a future patch. PR-URL: https://github.com/nodejs/node/pull/5756 Reviewed-By: Ben Noordhuis Reviewed-By: Andreas Madsen --- src/async-wrap.cc | 5 ++- src/node.cc | 7 +++- .../test-async-wrap-post-did-throw.js | 34 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-async-wrap-post-did-throw.js diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 570ed2f165b2ba..7338f051cd4aee 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -9,6 +9,7 @@ #include "v8-profiler.h" using v8::Array; +using v8::Boolean; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; @@ -231,7 +232,9 @@ Local AsyncWrap::MakeCallback(const Local cb, Local ret = cb->Call(context, argc, argv); if (ran_init_callback() && !post_fn.IsEmpty()) { - if (post_fn->Call(context, 1, &uid).IsEmpty()) + Local did_throw = Boolean::New(env()->isolate(), ret.IsEmpty()); + Local vals[] = { uid, did_throw }; + if (post_fn->Call(context, arraysize(vals), vals).IsEmpty()) FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); } diff --git a/src/node.cc b/src/node.cc index 074589542f59ba..0281f31d2bf47b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1172,7 +1172,12 @@ Local MakeCallback(Environment* env, Local ret = callback->Call(recv, argc, argv); if (ran_init_callback && !post_fn.IsEmpty()) { - if (post_fn->Call(object, 0, nullptr).IsEmpty()) + Local did_throw = Boolean::New(env->isolate(), ret.IsEmpty()); + // Currently there's no way to retrieve an uid from node::MakeCallback(). + // This needs to be fixed. + Local vals[] = + { Undefined(env->isolate()).As(), did_throw }; + if (post_fn->Call(object, arraysize(vals), vals).IsEmpty()) FatalError("node::MakeCallback", "post hook threw"); } diff --git a/test/parallel/test-async-wrap-post-did-throw.js b/test/parallel/test-async-wrap-post-did-throw.js new file mode 100644 index 00000000000000..9781983f58987e --- /dev/null +++ b/test/parallel/test-async-wrap-post-did-throw.js @@ -0,0 +1,34 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const async_wrap = process.binding('async_wrap'); +var asyncThrows = 0; +var uncaughtExceptionCount = 0; + +process.on('uncaughtException', (e) => { + assert.equal(e.message, 'oh noes!', 'error messages do not match'); +}); + +process.on('exit', () => { + process.removeAllListeners('uncaughtException'); + assert.equal(uncaughtExceptionCount, 1); + assert.equal(uncaughtExceptionCount, asyncThrows); +}); + +function init() { } +function post(id, threw) { + if (threw) + uncaughtExceptionCount++; +} + +async_wrap.setupHooks({ init, post }); +async_wrap.enable(); + +// Timers still aren't supported, so use crypto API. +// It's also important that the callback not happen in a nextTick, like many +// error events in core. +require('crypto').randomBytes(0, () => { + asyncThrows++; + throw new Error('oh noes!'); +}); From 8b1618a02254cff90571eba2cdad9f7c440844d9 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Mon, 14 Mar 2016 12:35:22 -0600 Subject: [PATCH 17/18] async_wrap: don't abort on callback exception Rather than abort if the init/pre/post/final/destroy callbacks throw, force the exception to propagate and not be made catchable. This way the application is still not allowed to proceed but also allowed the location of the failure to print before exiting. Though the stack itself may not be of much use since all callbacks except init are called from the bottom of the call stack. /tmp/async-test.js:14 throw new Error('pre'); ^ Error: pre at InternalFieldObject.pre (/tmp/async-test.js:14:9) PR-URL: https://github.com/nodejs/node/pull/5756 Reviewed-By: Ben Noordhuis Reviewed-By: Andreas Madsen --- src/async-wrap-inl.h | 15 ++-- src/async-wrap.cc | 20 ++++-- src/node.cc | 39 +++++++++-- src/node_internals.h | 5 ++ .../test-async-wrap-throw-from-callback.js | 68 +++++++++++++++++++ 5 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 test/parallel/test-async-wrap-throw-from-callback.js diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index eeea5973cac57b..cf7024e7e31461 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -51,11 +51,15 @@ inline AsyncWrap::AsyncWrap(Environment* env, argv[3] = parent->object(); } + v8::TryCatch try_catch(env->isolate()); + v8::MaybeLocal ret = init_fn->Call(env->context(), object, arraysize(argv), argv); - if (ret.IsEmpty()) - FatalError("node::AsyncWrap::AsyncWrap", "init hook threw"); + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + } bits_ |= 1; // ran_init_callback() is true now. } @@ -69,10 +73,13 @@ inline AsyncWrap::~AsyncWrap() { if (!fn.IsEmpty()) { v8::HandleScope scope(env()->isolate()); v8::Local uid = v8::Integer::New(env()->isolate(), get_uid()); + v8::TryCatch try_catch(env()->isolate()); v8::MaybeLocal ret = fn->Call(env()->context(), v8::Null(env()->isolate()), 1, &uid); - if (ret.IsEmpty()) - FatalError("node::AsyncWrap::~AsyncWrap", "destroy hook threw"); + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env()); + FatalException(env()->isolate(), try_catch); + } } } diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 7338f051cd4aee..8129500a922d97 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -18,6 +18,7 @@ using v8::HeapProfiler; using v8::Integer; using v8::Isolate; using v8::Local; +using v8::MaybeLocal; using v8::Object; using v8::RetainedObjectInfo; using v8::TryCatch; @@ -225,8 +226,13 @@ Local AsyncWrap::MakeCallback(const Local cb, } if (ran_init_callback() && !pre_fn.IsEmpty()) { - if (pre_fn->Call(context, 1, &uid).IsEmpty()) - FatalError("node::AsyncWrap::MakeCallback", "pre hook threw"); + TryCatch try_catch(env()->isolate()); + MaybeLocal ar = pre_fn->Call(env()->context(), context, 1, &uid); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env()); + FatalException(env()->isolate(), try_catch); + return Local(); + } } Local ret = cb->Call(context, argc, argv); @@ -234,8 +240,14 @@ Local AsyncWrap::MakeCallback(const Local cb, if (ran_init_callback() && !post_fn.IsEmpty()) { Local did_throw = Boolean::New(env()->isolate(), ret.IsEmpty()); Local vals[] = { uid, did_throw }; - if (post_fn->Call(context, arraysize(vals), vals).IsEmpty()) - FatalError("node::AsyncWrap::MakeCallback", "post hook threw"); + TryCatch try_catch(env()->isolate()); + MaybeLocal ar = + post_fn->Call(env()->context(), context, arraysize(vals), vals); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env()); + FatalException(env()->isolate(), try_catch); + return Local(); + } } if (ret.IsEmpty()) { diff --git a/src/node.cc b/src/node.cc index 0281f31d2bf47b..2f08a1da378eaf 100644 --- a/src/node.cc +++ b/src/node.cc @@ -115,6 +115,7 @@ using v8::Integer; using v8::Isolate; using v8::Local; using v8::Locker; +using v8::MaybeLocal; using v8::Message; using v8::Number; using v8::Object; @@ -1165,8 +1166,13 @@ Local MakeCallback(Environment* env, } if (ran_init_callback && !pre_fn.IsEmpty()) { - if (pre_fn->Call(object, 0, nullptr).IsEmpty()) - FatalError("node::MakeCallback", "pre hook threw"); + TryCatch try_catch(env->isolate()); + MaybeLocal ar = pre_fn->Call(env->context(), object, 0, nullptr); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + return Local(); + } } Local ret = callback->Call(recv, argc, argv); @@ -1177,8 +1183,14 @@ Local MakeCallback(Environment* env, // This needs to be fixed. Local vals[] = { Undefined(env->isolate()).As(), did_throw }; - if (post_fn->Call(object, arraysize(vals), vals).IsEmpty()) - FatalError("node::MakeCallback", "post hook threw"); + TryCatch try_catch(env->isolate()); + MaybeLocal ar = + post_fn->Call(env->context(), object, arraysize(vals), vals); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + return Local(); + } } if (ret.IsEmpty()) { @@ -2352,6 +2364,25 @@ void OnMessage(Local message, Local error) { } +void ClearFatalExceptionHandlers(Environment* env) { + Local process = env->process_object(); + Local events = + process->Get(env->context(), env->events_string()).ToLocalChecked(); + + if (events->IsObject()) { + events.As()->Set( + env->context(), + OneByteString(env->isolate(), "uncaughtException"), + Undefined(env->isolate())).FromJust(); + } + + process->Set( + env->context(), + env->domain_string(), + Undefined(env->isolate())).FromJust(); +} + + static void Binding(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); diff --git a/src/node_internals.h b/src/node_internals.h index 6e3f7204777835..33ae25d6062938 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -237,6 +237,11 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { Environment* env_; }; +// Clear any domain and/or uncaughtException handlers to force the error's +// propagation and shutdown the process. Use this to force the process to exit +// by clearing all callbacks that could handle the error. +void ClearFatalExceptionHandlers(Environment* env); + enum NodeInstanceType { MAIN, WORKER }; class NodeInstanceData { diff --git a/test/parallel/test-async-wrap-throw-from-callback.js b/test/parallel/test-async-wrap-throw-from-callback.js new file mode 100644 index 00000000000000..bfbe32c38b021a --- /dev/null +++ b/test/parallel/test-async-wrap-throw-from-callback.js @@ -0,0 +1,68 @@ +'use strict'; + +require('../common'); +const async_wrap = process.binding('async_wrap'); +const assert = require('assert'); +const crypto = require('crypto'); +const domain = require('domain'); +const spawn = require('child_process').spawn; +const callbacks = [ 'init', 'pre', 'post', 'destroy' ]; +const toCall = process.argv[2]; +var msgCalled = 0; +var msgReceived = 0; + +function init() { + if (toCall === 'init') + throw new Error('init'); +} +function pre() { + if (toCall === 'pre') + throw new Error('pre'); +} +function post() { + if (toCall === 'post') + throw new Error('post'); +} +function destroy() { + if (toCall === 'destroy') + throw new Error('destroy'); +} + +if (typeof process.argv[2] === 'string') { + async_wrap.setupHooks({ init, pre, post, destroy }); + async_wrap.enable(); + + process.on('uncaughtException', () => assert.ok(0, 'UNREACHABLE')); + + const d = domain.create(); + d.on('error', () => assert.ok(0, 'UNREACHABLE')); + d.run(() => { + // Using randomBytes because timers are not yet supported. + crypto.randomBytes(0, () => { }); + }); + +} else { + + process.on('exit', (code) => { + assert.equal(msgCalled, callbacks.length); + assert.equal(msgCalled, msgReceived); + }); + + callbacks.forEach((item) => { + msgCalled++; + + const child = spawn(process.execPath, [__filename, item]); + var errstring = ''; + + child.stderr.on('data', (data) => { + errstring += data.toString(); + }); + + child.on('close', (code) => { + if (errstring.includes('Error: ' + item)) + msgReceived++; + + assert.equal(code, 1, `${item} closed with code ${code}`); + }); + }); +} From 73c0b55d8e23dabfc2842ddb6138ef59912076a9 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 1 Jun 2016 12:33:33 -0600 Subject: [PATCH 18/18] async_wrap: pass uid to JS as double Passing the uid via v8::Integer::New() converts it to a uint32_t. Which will trim the value early. Instead use v8::Number::New() to convert the int64_t to a double so that JS can see the full 2^53 range of uid's. PR-URL: https://github.com/nodejs/node/pull/7096 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Andreas Madsen --- src/async-wrap-inl.h | 6 +++--- src/async-wrap.cc | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index cf7024e7e31461..9f520de4429622 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -40,14 +40,14 @@ inline AsyncWrap::AsyncWrap(Environment* env, v8::HandleScope scope(env->isolate()); v8::Local argv[] = { - v8::Integer::New(env->isolate(), get_uid()), + v8::Number::New(env->isolate(), get_uid()), v8::Int32::New(env->isolate(), provider), Null(env->isolate()), Null(env->isolate()) }; if (parent != nullptr) { - argv[2] = v8::Integer::New(env->isolate(), parent->get_uid()); + argv[2] = v8::Number::New(env->isolate(), parent->get_uid()); argv[3] = parent->object(); } @@ -72,7 +72,7 @@ inline AsyncWrap::~AsyncWrap() { v8::Local fn = env()->async_hooks_destroy_function(); if (!fn.IsEmpty()) { v8::HandleScope scope(env()->isolate()); - v8::Local uid = v8::Integer::New(env()->isolate(), get_uid()); + v8::Local uid = v8::Number::New(env()->isolate(), get_uid()); v8::TryCatch try_catch(env()->isolate()); v8::MaybeLocal ret = fn->Call(env()->context(), v8::Null(env()->isolate()), 1, &uid); diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 8129500a922d97..2b6839c05ee461 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -19,6 +19,7 @@ using v8::Integer; using v8::Isolate; using v8::Local; using v8::MaybeLocal; +using v8::Number; using v8::Object; using v8::RetainedObjectInfo; using v8::TryCatch; @@ -198,7 +199,7 @@ Local AsyncWrap::MakeCallback(const Local cb, Local pre_fn = env()->async_hooks_pre_function(); Local post_fn = env()->async_hooks_post_function(); - Local uid = Integer::New(env()->isolate(), get_uid()); + Local uid = Number::New(env()->isolate(), get_uid()); Local context = object(); Local domain; bool has_domain = false;