diff options
author | Trevor Norris <trev.norris@gmail.com> | 2017-03-07 22:40:18 +0300 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2017-05-10 23:22:20 +0300 |
commit | c0bde73f1bbfedd4e77ddf87cf0bcec7bac9a61e (patch) | |
tree | d0c2399a318f8d6a8ee5f1ab272604ad3171fc15 /src | |
parent | fe2df3b842205d8dedffcc83de387d43579ec760 (diff) |
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<v8::String>'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 <amwebdk@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/async-wrap-inl.h | 12 | ||||
-rw-r--r-- | src/async-wrap.cc | 288 | ||||
-rw-r--r-- | src/async-wrap.h | 31 | ||||
-rw-r--r-- | src/connection_wrap.cc | 13 | ||||
-rw-r--r-- | src/connection_wrap.h | 3 | ||||
-rw-r--r-- | src/env-inl.h | 155 | ||||
-rw-r--r-- | src/env.h | 111 | ||||
-rw-r--r-- | src/handle_wrap.cc | 5 | ||||
-rw-r--r-- | src/handle_wrap.h | 3 | ||||
-rw-r--r-- | src/js_stream.cc | 17 | ||||
-rw-r--r-- | src/js_stream.h | 2 | ||||
-rw-r--r-- | src/node.cc | 72 | ||||
-rw-r--r-- | src/node_http_parser.cc | 2 | ||||
-rw-r--r-- | src/pipe_wrap.cc | 20 | ||||
-rw-r--r-- | src/pipe_wrap.h | 3 | ||||
-rw-r--r-- | src/stream_base-inl.h | 3 | ||||
-rw-r--r-- | src/stream_base.cc | 16 | ||||
-rw-r--r-- | src/stream_wrap.cc | 6 | ||||
-rw-r--r-- | src/stream_wrap.h | 3 | ||||
-rw-r--r-- | src/tcp_wrap.cc | 25 | ||||
-rw-r--r-- | src/tcp_wrap.h | 2 | ||||
-rw-r--r-- | src/udp_wrap.cc | 23 | ||||
-rw-r--r-- | 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 8d7ada213d5..75306a3b0dd 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<bool>(bits_ & 1); +inline AsyncWrap::ProviderType AsyncWrap::provider_type() const { + return provider_type_; } -inline AsyncWrap::ProviderType AsyncWrap::provider_type() const { - return static_cast<ProviderType>(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 11ed67d2420..ab3bf5aa749 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<Value> wrapper) { // end RetainedAsyncInfo -static void EnableHooksJS(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - Local<Function> 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<Value>& args) { - Environment* env = Environment::GetCurrent(args); - env->async_hooks()->set_enable_callbacks(0); -} - - static void SetupHooks(const FunctionCallbackInfo<Value>& 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<Object> fn_obj = args[0].As<Object>(); - Local<Value> init_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "init")).ToLocalChecked(); - Local<Value> pre_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "pre")).ToLocalChecked(); - Local<Value> post_v = fn_obj->Get( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "post")).ToLocalChecked(); - Local<Value> 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<Function>()); - - if (pre_v->IsFunction()) - env->set_async_hooks_pre_function(pre_v.As<Function>()); - if (post_v->IsFunction()) - env->set_async_hooks_post_function(post_v.As<Function>()); - if (destroy_v->IsFunction()) - env->set_async_hooks_destroy_function(destroy_v.As<Function>()); +#define SET_HOOK_FN(name) \ + Local<Value> 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<Function>()); + + 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<Object> 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::PropertyAttribute>(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<ArrayBuffer> 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<ArrayBuffer> 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<Object> 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<Object> 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<Function>()); - env->set_async_hooks_pre_function(Local<Function>()); - env->set_async_hooks_post_function(Local<Function>()); + env->set_async_hooks_before_function(Local<Function>()); + env->set_async_hooks_after_function(Local<Function>()); env->set_async_hooks_destroy_function(Local<Function>()); } @@ -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<Function> fn = env->async_hooks_destroy_function(); - if (fn.IsEmpty()) - return env->destroy_ids_list()->clear(); - TryCatch try_catch(env->isolate()); std::vector<double> destroy_ids_list; @@ -262,64 +295,63 @@ void LoadAsyncWrapperInfo(Environment* env) { AsyncWrap::AsyncWrap(Environment* env, Local<Object> object, - ProviderType provider, - AsyncWrap* parent) - : BaseObject(env, object), bits_(static_cast<uint32_t>(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<Function> 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<Value> 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<Value> 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<Function> init_fn = env()->async_hooks_init_function(); -AsyncWrap::~AsyncWrap() { - if (!ran_init_callback()) - return; + Local<Value> 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<Value> 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<Value> AsyncWrap::MakeCallback(const Local<Function> cb, Local<Value>* argv) { CHECK(env()->context() == env()->isolate()->GetCurrentContext()); - Local<Function> pre_fn = env()->async_hooks_pre_function(); - Local<Function> post_fn = env()->async_hooks_post_function(); - Local<Value> uid = Number::New(env()->isolate(), get_id()); + AsyncHooks* async_hooks = env()->async_hooks(); Local<Object> context = object(); Local<Object> domain; + Local<Value> uid; bool has_domain = false; Environment::AsyncCallbackScope callback_scope(env()); @@ -357,9 +388,15 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> 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<Function> fn = env()->async_hooks_before_function(); TryCatch try_catch(env()->isolate()); - MaybeLocal<Value> ar = pre_fn->Call(env()->context(), context, 1, &uid); + MaybeLocal<Value> 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<Value> AsyncWrap::MakeCallback(const Local<Function> cb, } } - Local<Value> ret = cb->Call(context, argc, argv); + // Finally... Get to running the user's callback. + MaybeLocal<Value> ret = cb->Call(env()->context(), context, argc, argv); + + Local<Value> ret_v; + if (!ret.ToLocal(&ret_v)) { + return Local<Value>(); + } - if (ran_init_callback() && !post_fn.IsEmpty()) { - Local<Value> did_throw = Boolean::New(env()->isolate(), ret.IsEmpty()); - Local<Value> 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<Function> fn = env()->async_hooks_after_function(); TryCatch try_catch(env()->isolate()); - MaybeLocal<Value> ar = - post_fn->Call(env()->context(), context, arraysize(vals), vals); + MaybeLocal<Value> 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<Value> AsyncWrap::MakeCallback(const Local<Function> 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<Value> exit_v = domain->Get(env()->exit_string()); @@ -397,7 +442,7 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, } if (callback_scope.in_makecallback()) { - return ret; + return ret_v; } Environment::TickInfo* tick_info = env()->tick_info(); @@ -406,18 +451,29 @@ Local<Value> AsyncWrap::MakeCallback(const Local<Function> 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<Object> 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<Value>(); - } + MaybeLocal<Value> 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<Value>() : ret_v; } } // namespace node diff --git a/src/async-wrap.h b/src/async-wrap.h index 7ccae02cced..1fe0499468b 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -76,8 +76,7 @@ class AsyncWrap : public BaseObject { AsyncWrap(Environment* env, v8::Local<v8::Object> 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<v8::Value> MakeCallback(const v8::Local<v8::Function> cb, - int argc, - v8::Local<v8::Value>* argv); + int argc, + v8::Local<v8::Value>* argv); inline v8::Local<v8::Value> MakeCallback(const v8::Local<v8::String> symbol, - int argc, - v8::Local<v8::Value>* argv); + int argc, + v8::Local<v8::Value>* argv); inline v8::Local<v8::Value> MakeCallback(uint32_t index, - int argc, - v8::Local<v8::Value>* argv); + int argc, + v8::Local<v8::Value>* 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 020fe8b4c95..da65c493160 100644 --- a/src/connection_wrap.cc +++ b/src/connection_wrap.cc @@ -23,13 +23,11 @@ using v8::Value; template <typename WrapType, typename UVType> ConnectionWrap<WrapType, UVType>::ConnectionWrap(Environment* env, Local<Object> object, - ProviderType provider, - AsyncWrap* parent) + ProviderType provider) : StreamWrap(env, object, reinterpret_cast<uv_stream_t*>(&handle_), - provider, - parent) {} + provider) {} template <typename WrapType, typename UVType> @@ -53,6 +51,7 @@ void ConnectionWrap<WrapType, UVType>::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<Object> client_obj = WrapType::Instantiate(env, wrap_data); @@ -115,14 +114,12 @@ void ConnectionWrap<WrapType, UVType>::AfterConnect(uv_connect_t* req, template ConnectionWrap<PipeWrap, uv_pipe_t>::ConnectionWrap( Environment* env, Local<Object> object, - ProviderType provider, - AsyncWrap* parent); + ProviderType provider); template ConnectionWrap<TCPWrap, uv_tcp_t>::ConnectionWrap( Environment* env, Local<Object> object, - ProviderType provider, - AsyncWrap* parent); + ProviderType provider); template void ConnectionWrap<PipeWrap, uv_pipe_t>::OnConnection( uv_stream_t* handle, int status); diff --git a/src/connection_wrap.h b/src/connection_wrap.h index 7af97fd3f05..99fe5697ed9 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<v8::Object> object, - ProviderType provider, - AsyncWrap* parent); + ProviderType provider); ~ConnectionWrap() { } diff --git a/src/env-inl.h b/src/env-inl.h index d008586e5c6..edcdc410cc7 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<const uint8_t*>(#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<v8::String> 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<v8::Context> 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<Environment*>(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<double>* 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 8a07f38dfb8..1a8157949a1 100644 --- a/src/env.h +++ b/src/env.h @@ -39,6 +39,7 @@ #include <list> #include <stdint.h> #include <vector> +#include <stack> 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<v8::String> 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<v8::String> providers_[AsyncWrap::PROVIDERS_LENGTH]; + // Used by provider_string(). + v8::Isolate* isolate_; + // Stores the ids of the current execution context stack. + std::stack<struct node_async_ids> 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<double>* 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<double> destroy_ids_list_; #if HAVE_INSPECTOR inspector::Agent inspector_agent_; diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index da65586a7ed..7d0925e2fd6 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -90,9 +90,8 @@ void HandleWrap::Close(const FunctionCallbackInfo<Value>& args) { HandleWrap::HandleWrap(Environment* env, Local<Object> 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 280d60815e3..f8be356e1a7 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -74,8 +74,7 @@ class HandleWrap : public AsyncWrap { HandleWrap(Environment* env, v8::Local<v8::Object> 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 1d20e1c6d77..2644a6a451a 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<Object> obj, AsyncWrap* parent) - : AsyncWrap(env, obj, AsyncWrap::PROVIDER_JSSTREAM, parent), +JSStream::JSStream(Environment* env, Local<Object> obj) + : AsyncWrap(env, obj, AsyncWrap::PROVIDER_JSSTREAM), StreamBase(env) { node::Wrap(obj, this); MakeWeak<JSStream>(this); @@ -115,17 +114,7 @@ void JSStream::New(const FunctionCallbackInfo<Value>& 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<External>()->Value(); - wrap = new JSStream(env, args.This(), static_cast<AsyncWrap*>(ptr)); - } else { - UNREACHABLE(); - } - CHECK(wrap); + new JSStream(env, args.This()); } diff --git a/src/js_stream.h b/src/js_stream.h index 5a1244bc463..fc0b7abe15a 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<v8::Object> obj, AsyncWrap* parent); + JSStream(Environment* env, v8::Local<v8::Object> obj); AsyncWrap* GetAsyncWrap() override; diff --git a/src/node.cc b/src/node.cc index 8a992195105..770c68d5752 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<Value> MakeCallback(Environment* env, // If you hit this assertion, you forgot to enter the v8::Context first. CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); - Local<Function> pre_fn = env->async_hooks_pre_function(); - Local<Function> post_fn = env->async_hooks_post_function(); Local<Object> 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<Object>(); - Local<Value> 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<Value> MakeCallback(Environment* env, } } - if (ran_init_callback && !pre_fn.IsEmpty()) { - TryCatch try_catch(env->isolate()); - MaybeLocal<Value> ar = pre_fn->Call(env->context(), object, 0, nullptr); - if (ar.IsEmpty()) { - ClearFatalExceptionHandlers(env); - FatalException(env->isolate(), try_catch); - return Local<Value>(); - } - } + // 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<Value> ret = callback->Call(recv, argc, argv); - if (ran_init_callback && !post_fn.IsEmpty()) { - Local<Value> 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<Value> vals[] = - { Undefined(env->isolate()).As<Value>(), did_throw }; - TryCatch try_catch(env->isolate()); - MaybeLocal<Value> ar = - post_fn->Call(env->context(), object, arraysize(vals), vals); - if (ar.IsEmpty()) { - ClearFatalExceptionHandlers(env); - FatalException(env->isolate(), try_catch); - return Local<Value>(); - } - } - if (ret.IsEmpty()) { // NOTE: For backwards compatibility with public API we return Undefined() // if the top level call threw. @@ -1375,6 +1349,8 @@ Local<Value> MakeCallback(Environment* env, ret : Undefined(env->isolate()).As<Value>(); } + exec_scope.Dispose(); + if (has_domain) { Local<Value> exit_v = domain->Get(env->exit_string()); if (exit_v->IsFunction()) { @@ -1395,6 +1371,11 @@ Local<Value> 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<Object> process = env->process_object(); if (tick_info->length() == 0) { @@ -1411,10 +1392,10 @@ Local<Value> MakeCallback(Environment* env, Local<Value> MakeCallback(Environment* env, - Local<Object> recv, - Local<String> symbol, - int argc, - Local<Value> argv[]) { + Local<Object> recv, + Local<String> symbol, + int argc, + Local<Value> argv[]) { Local<Value> cb_v = recv->Get(symbol); CHECK(cb_v->IsFunction()); return MakeCallback(env, recv.As<Value>(), cb_v.As<Function>(), argc, argv); @@ -1422,10 +1403,10 @@ Local<Value> MakeCallback(Environment* env, Local<Value> MakeCallback(Environment* env, - Local<Object> recv, - const char* method, - int argc, - Local<Value> argv[]) { + Local<Object> recv, + const char* method, + int argc, + Local<Value> argv[]) { Local<String> 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 531a83392c2..01e7f6daca0 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 8c251f1f741..2185580b066 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<Object> 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<Function> constructor = env->pipe_constructor_template()->GetFunction(); CHECK_EQ(false, constructor.IsEmpty()); - Local<Value> ptr = External::New(env->isolate(), parent); Local<Object> 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<Value>& args) { // normal function. CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); - if (args[0]->IsExternal()) { - void* ptr = args[0].As<External>()->Value(); - new PipeWrap(env, args.This(), false, static_cast<AsyncWrap*>(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> 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 5ad6a9be1b2..6db7f4561cb 100644 --- a/src/pipe_wrap.h +++ b/src/pipe_wrap.h @@ -42,8 +42,7 @@ class PipeWrap : public ConnectionWrap<PipeWrap, uv_pipe_t> { private: PipeWrap(Environment* env, v8::Local<v8::Object> object, - bool ipc, - AsyncWrap* parent); + bool ipc); static void New(const v8::FunctionCallbackInfo<v8::Value>& args); static void Bind(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/src/stream_base-inl.h b/src/stream_base-inl.h index da636909b69..8b5b1542070 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 <class Base> void StreamBase::AddMethods(Environment* env, Local<FunctionTemplate> t, @@ -134,6 +136,7 @@ void StreamBase::JSMethod(const FunctionCallbackInfo<Value>& 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 19130b5bb8b..1cf1e44bd86 100644 --- a/src/stream_base.cc +++ b/src/stream_base.cc @@ -53,6 +53,9 @@ int StreamBase::Shutdown(const FunctionCallbackInfo<Value>& args) { CHECK(args[0]->IsObject()); Local<Object> req_wrap_obj = args[0].As<Object>(); + 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<Value>& 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<Value>& 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<char*>(data); @@ -216,6 +225,9 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo<Value>& 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<Value>& args) { Local<Object> req_wrap_obj = args[0].As<Object>(); Local<String> string = args[1].As<String>(); Local<Object> send_handle_obj; + AsyncWrap* wrap; if (args[2]->IsObject()) send_handle_obj = args[2].As<Object>(); @@ -297,6 +310,9 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& 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 065505af197..3497146cb07 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -86,13 +86,11 @@ void StreamWrap::Initialize(Local<Object> target, StreamWrap::StreamWrap(Environment* env, Local<Object> object, uv_stream_t* stream, - AsyncWrap::ProviderType provider, - AsyncWrap* parent) + AsyncWrap::ProviderType provider) : HandleWrap(env, object, reinterpret_cast<uv_handle_t*>(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 14ff18e7f39..161bcd550f6 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<v8::Object> 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 931b637751e..d1bf4a952a1 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<Object> 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<Function> constructor = env->tcp_constructor_template()->GetFunction(); CHECK_EQ(constructor.IsEmpty(), false); - Local<Value> ptr = External::New(env->isolate(), parent); Local<Object> 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<Value>& 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<External>()->Value(); - wrap = new TCPWrap(env, args.This(), static_cast<AsyncWrap*>(ptr)); - } else { - UNREACHABLE(); - } - CHECK(wrap); + new TCPWrap(env, args.This()); } -TCPWrap::TCPWrap(Environment* env, Local<Object> object, AsyncWrap* parent) +TCPWrap::TCPWrap(Environment* env, Local<Object> 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<Value>& 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<Value>& 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 a3cb2d524b8..95c0b1c1e5b 100644 --- a/src/tcp_wrap.h +++ b/src/tcp_wrap.h @@ -46,7 +46,7 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t> { int (*F)(const typename T::HandleType*, sockaddr*, int*)> friend void GetSockOrPeerName(const v8::FunctionCallbackInfo<v8::Value>&); - TCPWrap(Environment* env, v8::Local<v8::Object> object, AsyncWrap* parent); + TCPWrap(Environment* env, v8::Local<v8::Object> object); ~TCPWrap(); static void New(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index fe2b10661fd..c192de6d628 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<uv_udp_send_t> { public: @@ -88,7 +89,7 @@ static void NewSendWrap(const FunctionCallbackInfo<Value>& args) { } -UDPWrap::UDPWrap(Environment* env, Local<Object> object, AsyncWrap* parent) +UDPWrap::UDPWrap(Environment* env, Local<Object> object) : HandleWrap(env, object, reinterpret_cast<uv_handle_t*>(&handle_), @@ -155,15 +156,7 @@ void UDPWrap::Initialize(Local<Object> target, void UDPWrap::New(const FunctionCallbackInfo<Value>& 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<AsyncWrap*>(args[0].As<External>()->Value())); - } else { - UNREACHABLE(); - } + new UDPWrap(env, args.This()); } @@ -303,6 +296,7 @@ void UDPWrap::DoSend(const FunctionCallbackInfo<Value>& 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<Object> 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<Value> ptr = External::New(env->isolate(), parent); - return scope.Escape(env->udp_constructor_function() - ->NewInstance(env->context(), 1, &ptr).ToLocalChecked()); + Local<Object> 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 60bedace741..c8913d5da2e 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<v8::Value>&); - UDPWrap(Environment* env, v8::Local<v8::Object> object, AsyncWrap* parent); + UDPWrap(Environment* env, v8::Local<v8::Object> object); static void DoBind(const v8::FunctionCallbackInfo<v8::Value>& args, int family); |