From c0bde73f1bbfedd4e77ddf87cf0bcec7bac9a61e Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Tue, 7 Mar 2017 12:40:18 -0700 Subject: [PATCH] src: implement native changes for async_hooks Changes in the native code for the upcoming async_hooks module. These have been separated to help with review and testing. Changes include: * Introduce an async id stack that tracks recursive calls into async execution contexts. For performance reasons the id stack is held as a double* and assigned to a Float64Array. If the stack grows too large it is then placed in it's own stack and replaced with a new double*. This should accommodate arbitrarily large stacks. I'm not especially happy with the complexity involved with this async id stack, but it's also the fastest and most full proof way of handling it that I have found. * Add helper functions in Environment and AsyncWrap to work with the async id stack. * Add AsyncWrap::Reset() to allow AsyncWrap instances that have been placed in a resource pool, instead of being released, to be reinitialized. AsyncWrap::AsyncWrap() also now uses Reset() for initialization. * AsyncWrap* parent no longer needs to be passed via the constructor. * Introduce Environment::AsyncHooks class to contain the needed native functionality. This includes the pointer to the async id stack, and array of v8::Eternal's that hold the names of all providers, mechanisms for storing/retrieving the trigger id, etc. * Introduce Environment::AsyncHooks::ExecScope as a way to track the current id and trigger id of function execution via RAII. * If the user passes --abort-on-uncaught-exception then instead of throwing the application will print a stack trace and abort. PR-URL: https://github.com/nodejs/node/pull/12892 Ref: https://github.com/nodejs/node/pull/11883 Ref: https://github.com/nodejs/node/pull/8531 Reviewed-By: Andreas Madsen Reviewed-By: Anna Henningsen Reviewed-By: Sam Roberts Reviewed-By: Matteo Collina Reviewed-By: Refael Ackermann Reviewed-By: James M Snell Reviewed-By: Jeremiah Senkpiel --- src/async-wrap-inl.h | 12 +- src/async-wrap.cc | 288 ++++++++++++++++++++++++---------------- src/async-wrap.h | 31 ++--- src/connection_wrap.cc | 13 +- src/connection_wrap.h | 3 +- src/env-inl.h | 155 +++++++++++++++++++-- src/env.h | 111 +++++++++++++--- src/handle_wrap.cc | 5 +- src/handle_wrap.h | 3 +- src/js_stream.cc | 17 +-- src/js_stream.h | 2 +- src/node.cc | 72 +++++----- src/node_http_parser.cc | 2 + src/pipe_wrap.cc | 20 +-- src/pipe_wrap.h | 3 +- src/stream_base-inl.h | 3 + src/stream_base.cc | 16 +++ src/stream_wrap.cc | 6 +- src/stream_wrap.h | 3 +- src/tcp_wrap.cc | 25 ++-- src/tcp_wrap.h | 2 +- src/udp_wrap.cc | 23 ++-- src/udp_wrap.h | 2 +- 23 files changed, 525 insertions(+), 292 deletions(-) diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h index 8d7ada213d5611..75306a3b0ddfc1 100644 --- a/src/async-wrap-inl.h +++ b/src/async-wrap-inl.h @@ -36,18 +36,18 @@ namespace node { -inline bool AsyncWrap::ran_init_callback() const { - return static_cast(bits_ & 1); +inline AsyncWrap::ProviderType AsyncWrap::provider_type() const { + return provider_type_; } -inline AsyncWrap::ProviderType AsyncWrap::provider_type() const { - return static_cast(bits_ >> 1); +inline double AsyncWrap::get_id() const { + return async_id_; } -inline double AsyncWrap::get_id() const { - return id_; +inline double AsyncWrap::get_trigger_id() const { + return trigger_id_; } diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 11ed67d24207c0..ab3bf5aa74912e 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -30,13 +30,14 @@ #include "v8.h" #include "v8-profiler.h" -using v8::Boolean; +using v8::Array; +using v8::ArrayBuffer; using v8::Context; +using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::HeapProfiler; -using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Local; @@ -45,8 +46,11 @@ using v8::Number; using v8::Object; using v8::RetainedObjectInfo; using v8::TryCatch; +using v8::Uint32Array; using v8::Value; +using AsyncHooks = node::Environment::AsyncHooks; + namespace node { static const char* const provider_names[] = { @@ -57,6 +61,8 @@ static const char* const provider_names[] = { }; +// Report correct information in a heapdump. + class RetainedAsyncInfo: public RetainedObjectInfo { public: explicit RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap); @@ -128,55 +134,31 @@ RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local wrapper) { // end RetainedAsyncInfo -static void EnableHooksJS(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Local init_fn = env->async_hooks_init_function(); - if (init_fn.IsEmpty() || !init_fn->IsFunction()) - return env->ThrowTypeError("init callback is not assigned to a function"); - env->async_hooks()->set_enable_callbacks(1); -} - - -static void DisableHooksJS(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - env->async_hooks()->set_enable_callbacks(0); -} - - static void SetupHooks(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (env->async_hooks()->callbacks_enabled()) - return env->ThrowError("hooks should not be set while also enabled"); if (!args[0]->IsObject()) return env->ThrowTypeError("first argument must be an object"); + // All of init, before, after, destroy are supplied by async_hooks + // internally, so this should every only be called once. At which time all + // the functions should be set. Detect this by checking if init !IsEmpty(). + CHECK(env->async_hooks_init_function().IsEmpty()); + 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(init_v.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()); +#define SET_HOOK_FN(name) \ + Local name##_v = fn_obj->Get( \ + env->context(), \ + FIXED_ONE_BYTE_STRING(env->isolate(), #name)).ToLocalChecked(); \ + CHECK(name##_v->IsFunction()); \ + env->set_async_hooks_##name##_function(name##_v.As()); + + SET_HOOK_FN(init); + SET_HOOK_FN(before); + SET_HOOK_FN(after); + SET_HOOK_FN(destroy); +#undef SET_HOOK_FN } @@ -196,20 +178,76 @@ void AsyncWrap::Initialize(Local target, HandleScope scope(isolate); env->SetMethod(target, "setupHooks", SetupHooks); - env->SetMethod(target, "disable", DisableHooksJS); - env->SetMethod(target, "enable", EnableHooksJS); + + v8::PropertyAttribute ReadOnlyDontDelete = + static_cast(v8::ReadOnly | v8::DontDelete); + +#define FORCE_SET_TARGET_FIELD(obj, str, field) \ + (obj)->ForceSet(context, \ + FIXED_ONE_BYTE_STRING(isolate, str), \ + field, \ + ReadOnlyDontDelete).FromJust() + + // Attach the uint32_t[] where each slot contains the count of the number of + // callbacks waiting to be called on a particular event. It can then be + // incremented/decremented from JS quickly to communicate to C++ if there are + // any callbacks waiting to be called. + uint32_t* fields_ptr = env->async_hooks()->fields(); + int fields_count = env->async_hooks()->fields_count(); + Local fields_ab = + ArrayBuffer::New(isolate, fields_ptr, fields_count * sizeof(*fields_ptr)); + FORCE_SET_TARGET_FIELD(target, + "async_hook_fields", + Uint32Array::New(fields_ab, 0, fields_count)); + + // The following v8::Float64Array has 5 fields. These fields are shared in + // this way to allow JS and C++ to read/write each value as quickly as + // possible. The fields are represented as follows: + // + // kAsyncUid: Maintains the state of the next unique id to be assigned. + // + // kInitTriggerId: Write the id of the resource responsible for a handle's + // creation just before calling the new handle's constructor. After the new + // handle is constructed kInitTriggerId is set back to 0. + double* uid_fields_ptr = env->async_hooks()->uid_fields(); + int uid_fields_count = env->async_hooks()->uid_fields_count(); + Local uid_fields_ab = ArrayBuffer::New( + isolate, + uid_fields_ptr, + uid_fields_count * sizeof(*uid_fields_ptr)); + FORCE_SET_TARGET_FIELD(target, + "async_uid_fields", + Float64Array::New(uid_fields_ab, 0, uid_fields_count)); + + Local constants = Object::New(isolate); +#define SET_HOOKS_CONSTANT(name) \ + FORCE_SET_TARGET_FIELD( \ + constants, #name, Integer::New(isolate, AsyncHooks::name)); + + SET_HOOKS_CONSTANT(kInit); + SET_HOOKS_CONSTANT(kBefore); + SET_HOOKS_CONSTANT(kAfter); + SET_HOOKS_CONSTANT(kDestroy); + SET_HOOKS_CONSTANT(kCurrentAsyncId); + SET_HOOKS_CONSTANT(kCurrentTriggerId); + SET_HOOKS_CONSTANT(kAsyncUidCntr); + SET_HOOKS_CONSTANT(kInitTriggerId); +#undef SET_HOOKS_CONSTANT + FORCE_SET_TARGET_FIELD(target, "constants", constants); Local async_providers = Object::New(isolate); -#define V(PROVIDER) \ - async_providers->Set(FIXED_ONE_BYTE_STRING(isolate, #PROVIDER), \ - Integer::New(isolate, AsyncWrap::PROVIDER_ ## PROVIDER)); +#define V(p) \ + FORCE_SET_TARGET_FIELD( \ + async_providers, #p, Integer::New(isolate, AsyncWrap::PROVIDER_ ## p)); NODE_ASYNC_PROVIDER_TYPES(V) #undef V - target->Set(FIXED_ONE_BYTE_STRING(isolate, "Providers"), async_providers); + FORCE_SET_TARGET_FIELD(target, "Providers", async_providers); + +#undef FORCE_SET_TARGET_FIELD env->set_async_hooks_init_function(Local()); - env->set_async_hooks_pre_function(Local()); - env->set_async_hooks_post_function(Local()); + env->set_async_hooks_before_function(Local()); + env->set_async_hooks_after_function(Local()); env->set_async_hooks_destroy_function(Local()); } @@ -218,16 +256,11 @@ void AsyncWrap::DestroyIdsCb(uv_idle_t* handle) { uv_idle_stop(handle); Environment* env = Environment::from_destroy_ids_idle_handle(handle); - // None of the V8 calls done outside the HandleScope leak a handle. If this - // changes in the future then the SealHandleScope wrapping the uv_run() - // will catch this can cause the process to abort. + HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); Local fn = env->async_hooks_destroy_function(); - if (fn.IsEmpty()) - return env->destroy_ids_list()->clear(); - TryCatch try_catch(env->isolate()); std::vector destroy_ids_list; @@ -262,64 +295,63 @@ void LoadAsyncWrapperInfo(Environment* env) { AsyncWrap::AsyncWrap(Environment* env, Local object, - ProviderType provider, - AsyncWrap* parent) - : BaseObject(env, object), bits_(static_cast(provider) << 1), - id_(env->get_async_wrap_uid()) { + ProviderType provider) + : BaseObject(env, object), + provider_type_(provider) { CHECK_NE(provider, PROVIDER_NONE); CHECK_GE(object->InternalFieldCount(), 1); // Shift provider value over to prevent id collision. persistent().SetWrapperClassId(NODE_ASYNC_ID_OFFSET + provider); - Local init_fn = env->async_hooks_init_function(); + // Use AsyncReset() call to execute the init() callbacks. + AsyncReset(); +} - // No init callback exists, no reason to go on. - if (init_fn.IsEmpty()) - return; - // If async wrap callbacks are disabled and no parent was passed that has - // run the init callback then return. - if (!env->async_wrap_callbacks_enabled() && - (parent == nullptr || !parent->ran_init_callback())) +AsyncWrap::~AsyncWrap() { + if (env()->async_hooks()->fields()[AsyncHooks::kDestroy] == 0) { return; + } - HandleScope scope(env->isolate()); - - Local argv[] = { - Number::New(env->isolate(), get_id()), - Int32::New(env->isolate(), provider), - Null(env->isolate()), - Null(env->isolate()) - }; + if (env()->destroy_ids_list()->empty()) + uv_idle_start(env()->destroy_ids_idle_handle(), DestroyIdsCb); - if (parent != nullptr) { - argv[2] = Number::New(env->isolate(), parent->get_id()); - argv[3] = parent->object(); - } + env()->destroy_ids_list()->push_back(get_id()); +} - TryCatch try_catch(env->isolate()); - MaybeLocal ret = - init_fn->Call(env->context(), object, arraysize(argv), argv); +// Generalized call for both the constructor and for handles that are pooled +// and reused over their lifetime. This way a new uid can be assigned when +// the resource is pulled out of the pool and put back into use. +void AsyncWrap::Reset() { + AsyncHooks* async_hooks = env()->async_hooks(); + async_id_ = env()->new_async_id(); + trigger_id_ = env()->get_init_trigger_id(); - if (ret.IsEmpty()) { - ClearFatalExceptionHandlers(env); - FatalException(env->isolate(), try_catch); + // Nothing to execute, so can continue normally. + if (async_hooks->fields()[AsyncHooks::kInit] == 0) { + return; } - bits_ |= 1; // ran_init_callback() is true now. -} - + HandleScope scope(env()->isolate()); + Local init_fn = env()->async_hooks_init_function(); -AsyncWrap::~AsyncWrap() { - if (!ran_init_callback()) - return; + Local argv[] = { + Number::New(env()->isolate(), get_id()), + env()->async_hooks()->provider_string(provider_type()), + object(), + Number::New(env()->isolate(), get_trigger_id()), + }; - if (env()->destroy_ids_list()->empty()) - uv_idle_start(env()->destroy_ids_idle_handle(), DestroyIdsCb); + TryCatch try_catch(env()->isolate()); + MaybeLocal ret = init_fn->Call( + env()->context(), object(), arraysize(argv), argv); - env()->destroy_ids_list()->push_back(get_id()); + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env()); + FatalException(env()->isolate(), try_catch); + } } @@ -328,11 +360,10 @@ Local AsyncWrap::MakeCallback(const Local cb, Local* argv) { CHECK(env()->context() == env()->isolate()->GetCurrentContext()); - Local pre_fn = env()->async_hooks_pre_function(); - Local post_fn = env()->async_hooks_post_function(); - Local uid = Number::New(env()->isolate(), get_id()); + AsyncHooks* async_hooks = env()->async_hooks(); Local context = object(); Local domain; + Local uid; bool has_domain = false; Environment::AsyncCallbackScope callback_scope(env()); @@ -357,9 +388,15 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - if (ran_init_callback() && !pre_fn.IsEmpty()) { + // Want currentId() to return the correct value from the callbacks. + AsyncHooks::ExecScope exec_scope(env(), get_id(), get_trigger_id()); + + if (async_hooks->fields()[AsyncHooks::kBefore] > 0) { + uid = Number::New(env()->isolate(), get_id()); + Local fn = env()->async_hooks_before_function(); TryCatch try_catch(env()->isolate()); - MaybeLocal ar = pre_fn->Call(env()->context(), context, 1, &uid); + MaybeLocal ar = fn->Call( + env()->context(), Undefined(env()->isolate()), 1, &uid); if (ar.IsEmpty()) { ClearFatalExceptionHandlers(env()); FatalException(env()->isolate(), try_catch); @@ -367,14 +404,23 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - Local ret = cb->Call(context, argc, argv); + // Finally... Get to running the user's callback. + MaybeLocal ret = cb->Call(env()->context(), context, argc, argv); + + Local ret_v; + if (!ret.ToLocal(&ret_v)) { + return Local(); + } - if (ran_init_callback() && !post_fn.IsEmpty()) { - Local did_throw = Boolean::New(env()->isolate(), ret.IsEmpty()); - Local vals[] = { uid, did_throw }; + // If the callback failed then the after() hooks will be called at the end + // of _fatalException(). + if (async_hooks->fields()[AsyncHooks::kAfter] > 0) { + if (uid.IsEmpty()) + uid = Number::New(env()->isolate(), get_id()); + Local fn = env()->async_hooks_after_function(); TryCatch try_catch(env()->isolate()); - MaybeLocal ar = - post_fn->Call(env()->context(), context, arraysize(vals), vals); + MaybeLocal ar = fn->Call( + env()->context(), Undefined(env()->isolate()), 1, &uid); if (ar.IsEmpty()) { ClearFatalExceptionHandlers(env()); FatalException(env()->isolate(), try_catch); @@ -382,9 +428,8 @@ Local AsyncWrap::MakeCallback(const Local cb, } } - if (ret.IsEmpty()) { - return ret; - } + // The execution scope of the id and trigger_id only go this far. + exec_scope.Dispose(); if (has_domain) { Local exit_v = domain->Get(env()->exit_string()); @@ -397,7 +442,7 @@ Local AsyncWrap::MakeCallback(const Local cb, } if (callback_scope.in_makecallback()) { - return ret; + return ret_v; } Environment::TickInfo* tick_info = env()->tick_info(); @@ -406,18 +451,29 @@ Local AsyncWrap::MakeCallback(const Local cb, env()->isolate()->RunMicrotasks(); } + // Make sure the stack unwound properly. If there are nested MakeCallback's + // then it should return early and not reach this code. + CHECK_EQ(env()->current_async_id(), 0); + CHECK_EQ(env()->trigger_id(), 0); + Local process = env()->process_object(); if (tick_info->length() == 0) { tick_info->set_index(0); - return ret; + return ret_v; } - if (env()->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { - return Local(); - } + MaybeLocal rcheck = + env()->tick_callback_function()->Call(env()->context(), + process, + 0, + nullptr); + + // Make sure the stack unwound properly. + CHECK_EQ(env()->current_async_id(), 0); + CHECK_EQ(env()->trigger_id(), 0); - return ret; + return rcheck.IsEmpty() ? Local() : ret_v; } } // namespace node diff --git a/src/async-wrap.h b/src/async-wrap.h index 7ccae02cced05b..1fe0499468b485 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -76,8 +76,7 @@ class AsyncWrap : public BaseObject { AsyncWrap(Environment* env, v8::Local object, - ProviderType provider, - AsyncWrap* parent = nullptr); + ProviderType provider); virtual ~AsyncWrap(); @@ -93,28 +92,30 @@ class AsyncWrap : public BaseObject { inline double get_id() const; + inline double get_trigger_id() const; + + void Reset(); + // Only call these within a valid HandleScope. + // TODO(trevnorris): These should return a MaybeLocal. v8::Local MakeCallback(const v8::Local cb, - int argc, - v8::Local* argv); + int argc, + v8::Local* argv); inline v8::Local MakeCallback(const v8::Local symbol, - int argc, - v8::Local* argv); + int argc, + v8::Local* argv); inline v8::Local MakeCallback(uint32_t index, - int argc, - v8::Local* argv); + int argc, + v8::Local* argv); virtual size_t self_size() const = 0; private: inline AsyncWrap(); - inline bool ran_init_callback() const; - - // When the async hooks init JS function is called from the constructor it is - // expected the context object will receive a _asyncQueue object property - // that will be used to call pre/post in MakeCallback. - uint32_t bits_; - const double id_; + const ProviderType provider_type_; + // Because the values may be Reset(), cannot be made const. + double async_id_; + double trigger_id_; }; void LoadAsyncWrapperInfo(Environment* env); diff --git a/src/connection_wrap.cc b/src/connection_wrap.cc index 020fe8b4c9508c..da65c493160e93 100644 --- a/src/connection_wrap.cc +++ b/src/connection_wrap.cc @@ -23,13 +23,11 @@ using v8::Value; template ConnectionWrap::ConnectionWrap(Environment* env, Local object, - ProviderType provider, - AsyncWrap* parent) + ProviderType provider) : StreamWrap(env, object, reinterpret_cast(&handle_), - provider, - parent) {} + provider) {} template @@ -53,6 +51,7 @@ void ConnectionWrap::OnConnection(uv_stream_t* handle, }; if (status == 0) { + env->set_init_trigger_id(wrap_data->get_id()); // Instantiate the client javascript object and handle. Local client_obj = WrapType::Instantiate(env, wrap_data); @@ -115,14 +114,12 @@ void ConnectionWrap::AfterConnect(uv_connect_t* req, template ConnectionWrap::ConnectionWrap( Environment* env, Local object, - ProviderType provider, - AsyncWrap* parent); + ProviderType provider); template ConnectionWrap::ConnectionWrap( Environment* env, Local object, - ProviderType provider, - AsyncWrap* parent); + ProviderType provider); template void ConnectionWrap::OnConnection( uv_stream_t* handle, int status); diff --git a/src/connection_wrap.h b/src/connection_wrap.h index 7af97fd3f05e1b..99fe5697ed91fa 100644 --- a/src/connection_wrap.h +++ b/src/connection_wrap.h @@ -22,8 +22,7 @@ class ConnectionWrap : public StreamWrap { protected: ConnectionWrap(Environment* env, v8::Local object, - ProviderType provider, - AsyncWrap* parent); + ProviderType provider); ~ConnectionWrap() { } diff --git a/src/env-inl.h b/src/env-inl.h index d008586e5c6e48..edcdc410cc7a70 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -80,8 +80,29 @@ inline uint32_t* IsolateData::zero_fill_field() const { return zero_fill_field_; } -inline Environment::AsyncHooks::AsyncHooks() { - for (int i = 0; i < kFieldsCount; i++) fields_[i] = 0; +inline Environment::AsyncHooks::AsyncHooks(v8::Isolate* isolate) + : isolate_(isolate), + fields_(), + uid_fields_() { + v8::HandleScope handle_scope(isolate_); + + // kAsyncUidCntr should start at 1 because that'll be the id the execution + // context during bootstrap (code that runs before entering uv_run()). + uid_fields_[AsyncHooks::kAsyncUidCntr] = 1; + + // Create all the provider strings that will be passed to JS. Place them in + // an array so the array index matches the PROVIDER id offset. This way the + // strings can be retrieved quickly. +#define V(Provider) \ + providers_[AsyncWrap::PROVIDER_ ## Provider].Set( \ + isolate_, \ + v8::String::NewFromOneByte( \ + isolate_, \ + reinterpret_cast(#Provider), \ + v8::NewStringType::kInternalized, \ + sizeof(#Provider) - 1).ToLocalChecked()); + NODE_ASYNC_PROVIDER_TYPES(V) +#undef V } inline uint32_t* Environment::AsyncHooks::fields() { @@ -92,12 +113,94 @@ inline int Environment::AsyncHooks::fields_count() const { return kFieldsCount; } -inline bool Environment::AsyncHooks::callbacks_enabled() { - return fields_[kEnableCallbacks] != 0; +inline double* Environment::AsyncHooks::uid_fields() { + return uid_fields_; +} + +inline int Environment::AsyncHooks::uid_fields_count() const { + return kUidFieldsCount; +} + +inline v8::Local Environment::AsyncHooks::provider_string(int idx) { + return providers_[idx].Get(isolate_); +} + +inline void Environment::AsyncHooks::push_ids(double async_id, + double trigger_id) { + CHECK_GE(async_id, 0); + CHECK_GE(trigger_id, 0); + + ids_stack_.push({ uid_fields_[kCurrentAsyncId], + uid_fields_[kCurrentTriggerId] }); + uid_fields_[kCurrentAsyncId] = async_id; + uid_fields_[kCurrentTriggerId] = trigger_id; +} + +inline bool Environment::AsyncHooks::pop_ids(double async_id) { + // In case of an exception then this may have already been reset, if the + // stack was multiple MakeCallback()'s deep. + if (ids_stack_.empty()) return false; + + // Ask for the async_id to be restored as a sanity check that the stack + // hasn't been corrupted. + if (uid_fields_[kCurrentAsyncId] != async_id) { + fprintf(stderr, + "Error: async hook stack has become corrupted (" + "actual: %'.f, expected: %'.f)\n", + uid_fields_[kCurrentAsyncId], + async_id); + Environment* env = Environment::GetCurrent(isolate_); + DumpBacktrace(stderr); + fflush(stderr); + if (!env->abort_on_uncaught_exception()) + exit(1); + fprintf(stderr, "\n"); + fflush(stderr); + ABORT_NO_BACKTRACE(); + } + + auto ids = ids_stack_.top(); + ids_stack_.pop(); + uid_fields_[kCurrentAsyncId] = ids.async_id; + uid_fields_[kCurrentTriggerId] = ids.trigger_id; + return !ids_stack_.empty(); +} + +inline void Environment::AsyncHooks::clear_id_stack() { + while (!ids_stack_.empty()) + ids_stack_.pop(); + uid_fields_[kCurrentAsyncId] = 0; + uid_fields_[kCurrentTriggerId] = 0; +} + +inline Environment::AsyncHooks::InitScope::InitScope( + Environment* env, double init_trigger_id) + : env_(env), + uid_fields_(env->async_hooks()->uid_fields()) { + env->async_hooks()->push_ids(uid_fields_[AsyncHooks::kCurrentAsyncId], + init_trigger_id); +} + +inline Environment::AsyncHooks::InitScope::~InitScope() { + env_->async_hooks()->pop_ids(uid_fields_[AsyncHooks::kCurrentAsyncId]); } -inline void Environment::AsyncHooks::set_enable_callbacks(uint32_t flag) { - fields_[kEnableCallbacks] = flag; +inline Environment::AsyncHooks::ExecScope::ExecScope( + Environment* env, double async_id, double trigger_id) + : env_(env), + async_id_(async_id), + disposed_(false) { + env->async_hooks()->push_ids(async_id, trigger_id); +} + +inline Environment::AsyncHooks::ExecScope::~ExecScope() { + if (disposed_) return; + Dispose(); +} + +inline void Environment::AsyncHooks::ExecScope::Dispose() { + disposed_ = true; + env_->async_hooks()->pop_ids(async_id_); } inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env) @@ -187,12 +290,13 @@ inline Environment::Environment(IsolateData* isolate_data, v8::Local context) : isolate_(context->GetIsolate()), isolate_data_(isolate_data), + async_hooks_(context->GetIsolate()), timer_base_(uv_now(isolate_data->event_loop())), using_domains_(false), printed_error_(false), trace_sync_io_(false), + abort_on_uncaught_exception_(false), makecallback_cntr_(0), - async_wrap_id_(0), #if HAVE_INSPECTOR inspector_agent_(this), #endif @@ -231,11 +335,6 @@ inline v8::Isolate* Environment::isolate() const { return isolate_; } -inline bool Environment::async_wrap_callbacks_enabled() const { - // The const_cast is okay, it doesn't violate conceptual const-ness. - return const_cast(this)->async_hooks()->callbacks_enabled(); -} - inline bool Environment::in_domain() const { // The const_cast is okay, it doesn't violate conceptual const-ness. return using_domains() && @@ -314,14 +413,42 @@ inline void Environment::set_trace_sync_io(bool value) { trace_sync_io_ = value; } -inline double Environment::get_async_wrap_uid() { - return ++async_wrap_id_; +inline bool Environment::abort_on_uncaught_exception() const { + return abort_on_uncaught_exception_; +} + +inline void Environment::set_abort_on_uncaught_exception(bool value) { + abort_on_uncaught_exception_ = value; } inline std::vector* Environment::destroy_ids_list() { return &destroy_ids_list_; } +inline double Environment::new_async_id() { + return ++async_hooks()->uid_fields()[AsyncHooks::kAsyncUidCntr]; +} + +inline double Environment::current_async_id() { + return async_hooks()->uid_fields()[AsyncHooks::kCurrentAsyncId]; +} + +inline double Environment::trigger_id() { + return async_hooks()->uid_fields()[AsyncHooks::kCurrentTriggerId]; +} + +inline double Environment::get_init_trigger_id() { + double* uid_fields = async_hooks()->uid_fields(); + double tid = uid_fields[AsyncHooks::kInitTriggerId]; + uid_fields[AsyncHooks::kInitTriggerId] = 0; + if (tid <= 0) tid = current_async_id(); + return tid; +} + +inline void Environment::set_init_trigger_id(const double id) { + async_hooks()->uid_fields()[AsyncHooks::kInitTriggerId] = id; +} + inline double* Environment::heap_statistics_buffer() const { CHECK_NE(heap_statistics_buffer_, nullptr); return heap_statistics_buffer_; diff --git a/src/env.h b/src/env.h index 8a07f38dfb84db..1a8157949a13f4 100644 --- a/src/env.h +++ b/src/env.h @@ -39,6 +39,7 @@ #include #include #include +#include namespace node { @@ -87,7 +88,6 @@ namespace node { V(address_string, "address") \ V(args_string, "args") \ V(async, "async") \ - V(async_queue_string, "_asyncQueue") \ V(buffer_string, "buffer") \ V(bytes_string, "bytes") \ V(bytes_parsed_string, "bytesParsed") \ @@ -254,8 +254,8 @@ namespace node { V(as_external, v8::External) \ V(async_hooks_destroy_function, v8::Function) \ V(async_hooks_init_function, v8::Function) \ - V(async_hooks_post_function, v8::Function) \ - V(async_hooks_pre_function, v8::Function) \ + V(async_hooks_before_function, v8::Function) \ + V(async_hooks_after_function, v8::Function) \ V(binding_cache_object, v8::Object) \ V(buffer_constructor_function, v8::Function) \ V(buffer_prototype_object, v8::Object) \ @@ -291,6 +291,11 @@ struct node_ares_task { RB_ENTRY(node_ares_task) node; }; +struct node_async_ids { + double async_id; + double trigger_id; +}; + RB_HEAD(node_ares_task_list, node_ares_task); class IsolateData { @@ -331,31 +336,96 @@ class Environment { public: class AsyncHooks { public: + // Reason for both UidFields and Fields are that one is stored as a double* + // and the other as a uint32_t*. + enum Fields { + kInit, + kBefore, + kAfter, + kDestroy, + kFieldsCount, + }; + + enum UidFields { + kCurrentAsyncId, + kCurrentTriggerId, + kAsyncUidCntr, + kInitTriggerId, + kUidFieldsCount, + }; + + AsyncHooks() = delete; + inline uint32_t* fields(); inline int fields_count() const; - inline bool callbacks_enabled(); - inline void set_enable_callbacks(uint32_t flag); - - private: - friend class Environment; // So we can call the constructor. - inline AsyncHooks(); + inline double* uid_fields(); + inline int uid_fields_count() const; + inline v8::Local provider_string(int idx); + + inline void push_ids(double async_id, double trigger_id); + inline bool pop_ids(double async_id); + inline void clear_id_stack(); // Used in fatal exceptions. + + // Used to propagate the trigger_id to the constructor of any newly created + // resources using RAII. Instead of needing to pass the trigger_id along + // with other constructor arguments. + class InitScope { + public: + InitScope() = delete; + explicit InitScope(Environment* env, double init_trigger_id); + ~InitScope(); + + private: + Environment* env_; + double* uid_fields_; + + DISALLOW_COPY_AND_ASSIGN(InitScope); + }; - enum Fields { - // Set this to not zero if the init hook should be called. - kEnableCallbacks, - kFieldsCount + // Used to manage the stack of async and trigger ids as calls are made into + // JS. Mainly used in MakeCallback(). + class ExecScope { + public: + ExecScope() = delete; + explicit ExecScope(Environment* env, double async_id, double trigger_id); + ~ExecScope(); + void Dispose(); + + private: + Environment* env_; + double async_id_; + // Manually track if the destructor has run so it isn't accidentally run + // twice on RAII cleanup. + bool disposed_; + + DISALLOW_COPY_AND_ASSIGN(ExecScope); }; + private: + friend class Environment; // So we can call the constructor. + inline explicit AsyncHooks(v8::Isolate* isolate); + // Keep a list of all Persistent strings used for Provider types. + v8::Eternal providers_[AsyncWrap::PROVIDERS_LENGTH]; + // Used by provider_string(). + v8::Isolate* isolate_; + // Stores the ids of the current execution context stack. + std::stack ids_stack_; + // Used to communicate state between C++ and JS cheaply. Is placed in an + // Uint32Array() and attached to the async_wrap object. uint32_t fields_[kFieldsCount]; + // Used to communicate ids between C++ and JS cheaply. Placed in a + // Float64Array and attached to the async_wrap object. Using a double only + // gives us 2^53-1 unique ids, but that should be sufficient. + double uid_fields_[kUidFieldsCount]; DISALLOW_COPY_AND_ASSIGN(AsyncHooks); }; class AsyncCallbackScope { public: + AsyncCallbackScope() = delete; explicit AsyncCallbackScope(Environment* env); ~AsyncCallbackScope(); - inline bool in_makecallback(); private: @@ -452,7 +522,6 @@ class Environment { inline v8::Isolate* isolate() const; inline uv_loop_t* event_loop() const; - inline bool async_wrap_callbacks_enabled() const; inline bool in_domain() const; inline uint32_t watched_providers() const; @@ -489,7 +558,15 @@ class Environment { void PrintSyncTrace() const; inline void set_trace_sync_io(bool value); - inline double get_async_wrap_uid(); + inline bool abort_on_uncaught_exception() const; + inline void set_abort_on_uncaught_exception(bool value); + + // The necessary API for async_hooks. + inline double new_async_id(); + inline double current_async_id(); + inline double trigger_id(); + inline double get_init_trigger_id(); + inline void set_init_trigger_id(const double id); // List of id's that have been destroyed and need the destroy() cb called. inline std::vector* destroy_ids_list(); @@ -594,8 +671,8 @@ class Environment { bool using_domains_; bool printed_error_; bool trace_sync_io_; + bool abort_on_uncaught_exception_; size_t makecallback_cntr_; - double async_wrap_id_; std::vector destroy_ids_list_; #if HAVE_INSPECTOR inspector::Agent inspector_agent_; diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index da65586a7edbdf..7d0925e2fd6354 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -90,9 +90,8 @@ void HandleWrap::Close(const FunctionCallbackInfo& args) { HandleWrap::HandleWrap(Environment* env, Local object, uv_handle_t* handle, - AsyncWrap::ProviderType provider, - AsyncWrap* parent) - : AsyncWrap(env, object, provider, parent), + AsyncWrap::ProviderType provider) + : AsyncWrap(env, object, provider), state_(kInitialized), handle_(handle) { handle_->data = this; diff --git a/src/handle_wrap.h b/src/handle_wrap.h index 280d60815e3b52..f8be356e1a730c 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -74,8 +74,7 @@ class HandleWrap : public AsyncWrap { HandleWrap(Environment* env, v8::Local object, uv_handle_t* handle, - AsyncWrap::ProviderType provider, - AsyncWrap* parent = nullptr); + AsyncWrap::ProviderType provider); ~HandleWrap() override; private: diff --git a/src/js_stream.cc b/src/js_stream.cc index 1d20e1c6d77dfb..2644a6a451a00f 100644 --- a/src/js_stream.cc +++ b/src/js_stream.cc @@ -12,7 +12,6 @@ namespace node { using v8::Array; using v8::Context; -using v8::External; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; @@ -21,8 +20,8 @@ using v8::Object; using v8::Value; -JSStream::JSStream(Environment* env, Local obj, AsyncWrap* parent) - : AsyncWrap(env, obj, AsyncWrap::PROVIDER_JSSTREAM, parent), +JSStream::JSStream(Environment* env, Local obj) + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_JSSTREAM), StreamBase(env) { node::Wrap(obj, this); MakeWeak(this); @@ -115,17 +114,7 @@ void JSStream::New(const FunctionCallbackInfo& args) { // normal function. CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - JSStream* wrap; - - if (args.Length() == 0) { - wrap = new JSStream(env, args.This(), nullptr); - } else if (args[0]->IsExternal()) { - void* ptr = args[0].As()->Value(); - wrap = new JSStream(env, args.This(), static_cast(ptr)); - } else { - UNREACHABLE(); - } - CHECK(wrap); + new JSStream(env, args.This()); } diff --git a/src/js_stream.h b/src/js_stream.h index 5a1244bc463e36..fc0b7abe15a633 100644 --- a/src/js_stream.h +++ b/src/js_stream.h @@ -33,7 +33,7 @@ class JSStream : public AsyncWrap, public StreamBase { size_t self_size() const override { return sizeof(*this); } protected: - JSStream(Environment* env, v8::Local obj, AsyncWrap* parent); + JSStream(Environment* env, v8::Local obj); AsyncWrap* GetAsyncWrap() override; diff --git a/src/node.cc b/src/node.cc index 8a99219510511f..770c68d57520d6 100644 --- a/src/node.cc +++ b/src/node.cc @@ -154,6 +154,8 @@ using v8::Uint32Array; using v8::V8; using v8::Value; +using AsyncHooks = node::Environment::AsyncHooks; + static bool print_eval = false; static bool force_repl = false; static bool syntax_check_only = false; @@ -174,6 +176,7 @@ static node_module* modlist_linked; static node_module* modlist_addon; static bool trace_enabled = false; static std::string trace_enabled_categories; // NOLINT(runtime/string) +static bool abort_on_uncaught_exception = false; #if defined(NODE_HAVE_I18N_SUPPORT) // Path to ICU data (for i18n / Intl) @@ -1302,21 +1305,13 @@ Local MakeCallback(Environment* env, // If you hit this assertion, you forgot to enter the v8::Context first. CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); - Local pre_fn = env->async_hooks_pre_function(); - Local post_fn = env->async_hooks_post_function(); Local object, domain; - 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()) { object = recv.As(); - Local async_queue_v = object->Get(env->async_queue_string()); - if (async_queue_v->IsObject()) - ran_init_callback = true; } if (env->using_domains()) { @@ -1340,34 +1335,13 @@ Local MakeCallback(Environment* env, } } - if (ran_init_callback && !pre_fn.IsEmpty()) { - 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(); - } - } + // TODO(trevnorris): Correct this once node::MakeCallback() support id and + // triggerId. Consider completely removing it until then so the async id can + // propagate through to the fatalException after hook calls. + AsyncHooks::ExecScope exec_scope(env, 0, 0); Local ret = callback->Call(recv, argc, argv); - if (ran_init_callback && !post_fn.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 }; - 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()) { // NOTE: For backwards compatibility with public API we return Undefined() // if the top level call threw. @@ -1375,6 +1349,8 @@ Local MakeCallback(Environment* env, ret : Undefined(env->isolate()).As(); } + exec_scope.Dispose(); + if (has_domain) { Local exit_v = domain->Get(env->exit_string()); if (exit_v->IsFunction()) { @@ -1395,6 +1371,11 @@ Local MakeCallback(Environment* env, env->isolate()->RunMicrotasks(); } + // Make sure the stack unwound properly. If there are nested MakeCallback's + // then it should return early and not reach this code. + CHECK_EQ(env->current_async_id(), 0); + CHECK_EQ(env->trigger_id(), 0); + Local process = env->process_object(); if (tick_info->length() == 0) { @@ -1411,10 +1392,10 @@ Local MakeCallback(Environment* env, Local MakeCallback(Environment* env, - Local recv, - Local symbol, - int argc, - Local argv[]) { + Local recv, + Local symbol, + int argc, + Local argv[]) { Local cb_v = recv->Get(symbol); CHECK(cb_v->IsFunction()); return MakeCallback(env, recv.As(), cb_v.As(), argc, argv); @@ -1422,10 +1403,10 @@ Local MakeCallback(Environment* env, Local MakeCallback(Environment* env, - Local recv, - const char* method, - int argc, - Local argv[]) { + Local recv, + const char* method, + int argc, + Local argv[]) { Local method_string = OneByteString(env->isolate(), method); return MakeCallback(env, recv, method_string, argc, argv); } @@ -3925,6 +3906,12 @@ static void ParseArgs(int* argc, } else if (strcmp(arg, "--") == 0) { index += 1; break; + } else if (strcmp(arg, "--abort-on-uncaught-exception") || + strcmp(arg, "--abort_on_uncaught_exception")) { + abort_on_uncaught_exception = true; + // Also a V8 option. Pass through as-is. + new_v8_argv[new_v8_argc] = arg; + new_v8_argc += 1; } else { // V8 option. Pass through as-is. new_v8_argv[new_v8_argc] = arg; @@ -4471,8 +4458,11 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, if (debug_options.inspector_enabled() && !debugger_running) return 12; // Signal internal error. + env.set_abort_on_uncaught_exception(abort_on_uncaught_exception); + { Environment::AsyncCallbackScope callback_scope(&env); + Environment::AsyncHooks::ExecScope exec_scope(&env, 1, 0); LoadEnvironment(&env); } diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 531a83392c291f..01e7f6daca0c99 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -476,6 +476,8 @@ class Parser : public AsyncWrap { ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); // Should always be called from the same context. CHECK_EQ(env, parser->env()); + // The parser is being reused. Reset the uid and call init() callbacks. + parser->Reset(); parser->Init(type); } diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index 8c251f1f741461..2185580b0662e8 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -38,7 +38,6 @@ namespace node { using v8::Context; using v8::EscapableHandleScope; -using v8::External; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -47,15 +46,17 @@ using v8::Local; using v8::Object; using v8::Value; +using AsyncHooks = Environment::AsyncHooks; + Local PipeWrap::Instantiate(Environment* env, AsyncWrap* parent) { EscapableHandleScope handle_scope(env->isolate()); + AsyncHooks::InitScope init_scope(env, parent->get_id()); CHECK_EQ(false, env->pipe_constructor_template().IsEmpty()); Local constructor = env->pipe_constructor_template()->GetFunction(); CHECK_EQ(false, constructor.IsEmpty()); - Local ptr = External::New(env->isolate(), parent); Local instance = - constructor->NewInstance(env->context(), 1, &ptr).ToLocalChecked(); + constructor->NewInstance(env->context()).ToLocalChecked(); return handle_scope.Escape(instance); } @@ -114,23 +115,16 @@ void PipeWrap::New(const FunctionCallbackInfo& args) { // normal function. CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - if (args[0]->IsExternal()) { - void* ptr = args[0].As()->Value(); - new PipeWrap(env, args.This(), false, static_cast(ptr)); - } else { - new PipeWrap(env, args.This(), args[0]->IsTrue(), nullptr); - } + new PipeWrap(env, args.This(), args[0]->IsTrue()); } PipeWrap::PipeWrap(Environment* env, Local object, - bool ipc, - AsyncWrap* parent) + bool ipc) : ConnectionWrap(env, object, - AsyncWrap::PROVIDER_PIPEWRAP, - parent) { + AsyncWrap::PROVIDER_PIPEWRAP) { int r = uv_pipe_init(env->event_loop(), &handle_, ipc); CHECK_EQ(r, 0); // How do we proxy this error up to javascript? // Suggestion: uv_pipe_init() returns void. diff --git a/src/pipe_wrap.h b/src/pipe_wrap.h index 5ad6a9be1b2644..6db7f4561cb522 100644 --- a/src/pipe_wrap.h +++ b/src/pipe_wrap.h @@ -42,8 +42,7 @@ class PipeWrap : public ConnectionWrap { private: PipeWrap(Environment* env, v8::Local object, - bool ipc, - AsyncWrap* parent); + bool ipc); static void New(const v8::FunctionCallbackInfo& args); static void Bind(const v8::FunctionCallbackInfo& args); diff --git a/src/stream_base-inl.h b/src/stream_base-inl.h index da636909b695f3..8b5b15420703ef 100644 --- a/src/stream_base-inl.h +++ b/src/stream_base-inl.h @@ -23,6 +23,8 @@ using v8::PropertyCallbackInfo; using v8::String; using v8::Value; +using AsyncHooks = Environment::AsyncHooks; + template void StreamBase::AddMethods(Environment* env, Local t, @@ -134,6 +136,7 @@ void StreamBase::JSMethod(const FunctionCallbackInfo& args) { if (!wrap->IsAlive()) return args.GetReturnValue().Set(UV_EINVAL); + AsyncHooks::InitScope init_scope(handle->env(), handle->get_id()); args.GetReturnValue().Set((wrap->*Method)(args)); } diff --git a/src/stream_base.cc b/src/stream_base.cc index 19130b5bb8bd7e..1cf1e44bd86274 100644 --- a/src/stream_base.cc +++ b/src/stream_base.cc @@ -53,6 +53,9 @@ int StreamBase::Shutdown(const FunctionCallbackInfo& args) { CHECK(args[0]->IsObject()); Local req_wrap_obj = args[0].As(); + AsyncWrap* wrap = GetAsyncWrap(); + if (wrap != nullptr) + env->set_init_trigger_id(wrap->get_id()); ShutdownWrap* req_wrap = new ShutdownWrap(env, req_wrap_obj, this, @@ -129,6 +132,11 @@ int StreamBase::Writev(const FunctionCallbackInfo& args) { if (storage_size > INT_MAX) return UV_ENOBUFS; + AsyncWrap* wrap = GetAsyncWrap(); + // NOTE: All tests show that GetAsyncWrap() never returns nullptr here. If it + // can then replace the CHECK_NE() with if (wrap != nullptr). + CHECK_NE(wrap, nullptr); + env->set_init_trigger_id(wrap->get_id()); WriteWrap* req_wrap = WriteWrap::New(env, req_wrap_obj, this, @@ -201,6 +209,7 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo& args) { const char* data = Buffer::Data(args[1]); size_t length = Buffer::Length(args[1]); + AsyncWrap* wrap; WriteWrap* req_wrap; uv_buf_t buf; buf.base = const_cast(data); @@ -216,6 +225,9 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo& args) { goto done; CHECK_EQ(count, 1); + wrap = GetAsyncWrap(); + if (wrap != nullptr) + env->set_init_trigger_id(wrap->get_id()); // Allocate, or write rest req_wrap = WriteWrap::New(env, req_wrap_obj, this, AfterWrite); @@ -247,6 +259,7 @@ int StreamBase::WriteString(const FunctionCallbackInfo& args) { Local req_wrap_obj = args[0].As(); Local string = args[1].As(); Local send_handle_obj; + AsyncWrap* wrap; if (args[2]->IsObject()) send_handle_obj = args[2].As(); @@ -297,6 +310,9 @@ int StreamBase::WriteString(const FunctionCallbackInfo& args) { CHECK_EQ(count, 1); } + wrap = GetAsyncWrap(); + if (wrap != nullptr) + env->set_init_trigger_id(wrap->get_id()); req_wrap = WriteWrap::New(env, req_wrap_obj, this, AfterWrite, storage_size); data = req_wrap->Extra(); diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index 065505af1971ac..3497146cb07983 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -86,13 +86,11 @@ void StreamWrap::Initialize(Local target, StreamWrap::StreamWrap(Environment* env, Local object, uv_stream_t* stream, - AsyncWrap::ProviderType provider, - AsyncWrap* parent) + AsyncWrap::ProviderType provider) : HandleWrap(env, object, reinterpret_cast(stream), - provider, - parent), + provider), StreamBase(env), stream_(stream) { set_after_write_cb({ OnAfterWriteImpl, this }); diff --git a/src/stream_wrap.h b/src/stream_wrap.h index 14ff18e7f3930b..161bcd550f65f1 100644 --- a/src/stream_wrap.h +++ b/src/stream_wrap.h @@ -81,8 +81,7 @@ class StreamWrap : public HandleWrap, public StreamBase { StreamWrap(Environment* env, v8::Local object, uv_stream_t* stream, - AsyncWrap::ProviderType provider, - AsyncWrap* parent = nullptr); + AsyncWrap::ProviderType provider); ~StreamWrap() { } diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index 931b637751e610..d1bf4a952a1f7c 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -40,7 +40,6 @@ namespace node { using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; -using v8::External; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -51,15 +50,17 @@ using v8::Object; using v8::String; using v8::Value; +using AsyncHooks = Environment::AsyncHooks; + Local TCPWrap::Instantiate(Environment* env, AsyncWrap* parent) { EscapableHandleScope handle_scope(env->isolate()); + AsyncHooks::InitScope init_scope(env, parent->get_id()); CHECK_EQ(env->tcp_constructor_template().IsEmpty(), false); Local constructor = env->tcp_constructor_template()->GetFunction(); CHECK_EQ(constructor.IsEmpty(), false); - Local ptr = External::New(env->isolate(), parent); Local instance = - constructor->NewInstance(env->context(), 1, &ptr).ToLocalChecked(); + constructor->NewInstance(env->context()).ToLocalChecked(); return handle_scope.Escape(instance); } @@ -134,24 +135,14 @@ void TCPWrap::New(const FunctionCallbackInfo& args) { // normal function. CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - TCPWrap* wrap; - if (args.Length() == 0) { - wrap = new TCPWrap(env, args.This(), nullptr); - } else if (args[0]->IsExternal()) { - void* ptr = args[0].As()->Value(); - wrap = new TCPWrap(env, args.This(), static_cast(ptr)); - } else { - UNREACHABLE(); - } - CHECK(wrap); + new TCPWrap(env, args.This()); } -TCPWrap::TCPWrap(Environment* env, Local object, AsyncWrap* parent) +TCPWrap::TCPWrap(Environment* env, Local object) : ConnectionWrap(env, object, - AsyncWrap::PROVIDER_TCPWRAP, - parent) { + AsyncWrap::PROVIDER_TCPWRAP) { int r = uv_tcp_init(env->event_loop(), &handle_); CHECK_EQ(r, 0); // How do we proxy this error up to javascript? // Suggestion: uv_tcp_init() returns void. @@ -279,6 +270,7 @@ void TCPWrap::Connect(const FunctionCallbackInfo& args) { int err = uv_ip4_addr(*ip_address, port, &addr); if (err == 0) { + env->set_init_trigger_id(wrap->get_id()); ConnectWrap* req_wrap = new ConnectWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_TCPCONNECTWRAP); err = uv_tcp_connect(req_wrap->req(), @@ -314,6 +306,7 @@ void TCPWrap::Connect6(const FunctionCallbackInfo& args) { int err = uv_ip6_addr(*ip_address, port, &addr); if (err == 0) { + env->set_init_trigger_id(wrap->get_id()); ConnectWrap* req_wrap = new ConnectWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_TCPCONNECTWRAP); err = uv_tcp_connect(req_wrap->req(), diff --git a/src/tcp_wrap.h b/src/tcp_wrap.h index a3cb2d524b82a9..95c0b1c1e5b99e 100644 --- a/src/tcp_wrap.h +++ b/src/tcp_wrap.h @@ -46,7 +46,7 @@ class TCPWrap : public ConnectionWrap { int (*F)(const typename T::HandleType*, sockaddr*, int*)> friend void GetSockOrPeerName(const v8::FunctionCallbackInfo&); - TCPWrap(Environment* env, v8::Local object, AsyncWrap* parent); + TCPWrap(Environment* env, v8::Local object); ~TCPWrap(); static void New(const v8::FunctionCallbackInfo& args); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index fe2b10661fd7c3..c192de6d628cec 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -37,7 +37,6 @@ namespace node { using v8::Array; using v8::Context; using v8::EscapableHandleScope; -using v8::External; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; @@ -50,6 +49,8 @@ using v8::String; using v8::Undefined; using v8::Value; +using AsyncHooks = Environment::AsyncHooks; + class SendWrap : public ReqWrap { public: @@ -88,7 +89,7 @@ static void NewSendWrap(const FunctionCallbackInfo& args) { } -UDPWrap::UDPWrap(Environment* env, Local object, AsyncWrap* parent) +UDPWrap::UDPWrap(Environment* env, Local object) : HandleWrap(env, object, reinterpret_cast(&handle_), @@ -155,15 +156,7 @@ void UDPWrap::Initialize(Local target, void UDPWrap::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - if (args.Length() == 0) { - new UDPWrap(env, args.This(), nullptr); - } else if (args[0]->IsExternal()) { - new UDPWrap(env, - args.This(), - static_cast(args[0].As()->Value())); - } else { - UNREACHABLE(); - } + new UDPWrap(env, args.This()); } @@ -303,6 +296,7 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { node::Utf8Value address(env->isolate(), args[4]); const bool have_callback = args[5]->IsTrue(); + env->set_init_trigger_id(wrap->get_id()); SendWrap* req_wrap = new SendWrap(env, req_wrap_obj, have_callback); size_t msg_size = 0; @@ -450,11 +444,12 @@ void UDPWrap::OnRecv(uv_udp_t* handle, Local UDPWrap::Instantiate(Environment* env, AsyncWrap* parent) { EscapableHandleScope scope(env->isolate()); + AsyncHooks::InitScope init_scope(env, parent->get_id()); // If this assert fires then Initialize hasn't been called yet. CHECK_EQ(env->udp_constructor_function().IsEmpty(), false); - Local ptr = External::New(env->isolate(), parent); - return scope.Escape(env->udp_constructor_function() - ->NewInstance(env->context(), 1, &ptr).ToLocalChecked()); + Local instance = env->udp_constructor_function() + ->NewInstance(env->context()).ToLocalChecked(); + return scope.Escape(instance); } diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 60bedace7410df..c8913d5da2e107 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -68,7 +68,7 @@ class UDPWrap: public HandleWrap { int (*F)(const typename T::HandleType*, sockaddr*, int*)> friend void GetSockOrPeerName(const v8::FunctionCallbackInfo&); - UDPWrap(Environment* env, v8::Local object, AsyncWrap* parent); + UDPWrap(Environment* env, v8::Local object); static void DoBind(const v8::FunctionCallbackInfo& args, int family);