diff --git a/src/async-wrap.cc b/src/async-wrap.cc index d3776c7333..2efe0e23ec 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -47,6 +47,7 @@ using v8::Object; using v8::Promise; using v8::PromiseHookType; using v8::RetainedObjectInfo; +using v8::String; using v8::Symbol; using v8::TryCatch; using v8::Uint32Array; @@ -216,23 +217,28 @@ bool DomainExit(Environment* env, v8::Local object) { static bool PreCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) { - AsyncHooks* async_hooks = wrap->env()->async_hooks(); - if (wrap->env()->using_domains() && run_domain_cbs) { bool is_disposed = DomainEnter(wrap->env(), wrap->object()); if (is_disposed) return false; } + return AsyncWrap::EmitBefore(wrap->env(), wrap->get_id()); +} + + +bool AsyncWrap::EmitBefore(Environment* env, double async_id) { + AsyncHooks* async_hooks = env->async_hooks(); + if (async_hooks->fields()[AsyncHooks::kBefore] > 0) { - Local uid = Number::New(wrap->env()->isolate(), wrap->get_id()); - Local fn = wrap->env()->async_hooks_before_function(); - TryCatch try_catch(wrap->env()->isolate()); + Local uid = Number::New(env->isolate(), async_id); + Local fn = env->async_hooks_before_function(); + TryCatch try_catch(env->isolate()); MaybeLocal ar = fn->Call( - wrap->env()->context(), Undefined(wrap->env()->isolate()), 1, &uid); + env->context(), Undefined(env->isolate()), 1, &uid); if (ar.IsEmpty()) { - ClearFatalExceptionHandlers(wrap->env()); - FatalException(wrap->env()->isolate(), try_catch); + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); return false; } } @@ -242,29 +248,36 @@ static bool PreCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) { static bool PostCallbackExecution(AsyncWrap* wrap, bool run_domain_cbs) { - AsyncHooks* async_hooks = wrap->env()->async_hooks(); + if (!AsyncWrap::EmitAfter(wrap->env(), wrap->get_id())) + return false; + + if (wrap->env()->using_domains() && run_domain_cbs) { + bool is_disposed = DomainExit(wrap->env(), wrap->object()); + if (is_disposed) + return false; + } + + return true; +} + +bool AsyncWrap::EmitAfter(Environment* env, double async_id) { + AsyncHooks* async_hooks = env->async_hooks(); // If the callback failed then the after() hooks will be called at the end // of _fatalException(). if (async_hooks->fields()[AsyncHooks::kAfter] > 0) { - Local uid = Number::New(wrap->env()->isolate(), wrap->get_id()); - Local fn = wrap->env()->async_hooks_after_function(); - TryCatch try_catch(wrap->env()->isolate()); + Local uid = Number::New(env->isolate(), async_id); + Local fn = env->async_hooks_after_function(); + TryCatch try_catch(env->isolate()); MaybeLocal ar = fn->Call( - wrap->env()->context(), Undefined(wrap->env()->isolate()), 1, &uid); + env->context(), Undefined(env->isolate()), 1, &uid); if (ar.IsEmpty()) { - ClearFatalExceptionHandlers(wrap->env()); - FatalException(wrap->env()->isolate(), try_catch); + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); return false; } } - if (wrap->env()->using_domains() && run_domain_cbs) { - bool is_disposed = DomainExit(wrap->env(), wrap->object()); - if (is_disposed) - return false; - } - return true; } @@ -526,32 +539,44 @@ AsyncWrap::~AsyncWrap() { // 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::AsyncReset() { - AsyncHooks* async_hooks = env()->async_hooks(); async_id_ = env()->new_async_id(); trigger_id_ = env()->get_init_trigger_id(); + EmitAsyncInit(env(), object(), + env()->async_hooks()->provider_string(provider_type()), + async_id_, trigger_id_); +} + + +void AsyncWrap::EmitAsyncInit(Environment* env, + Local object, + Local type, + double async_id, + double trigger_id) { + AsyncHooks* async_hooks = env->async_hooks(); + // Nothing to execute, so can continue normally. if (async_hooks->fields()[AsyncHooks::kInit] == 0) { return; } - HandleScope scope(env()->isolate()); - Local init_fn = env()->async_hooks_init_function(); + HandleScope scope(env->isolate()); + Local init_fn = env->async_hooks_init_function(); Local argv[] = { - Number::New(env()->isolate(), get_id()), - env()->async_hooks()->provider_string(provider_type()), - object(), - Number::New(env()->isolate(), get_trigger_id()), + Number::New(env->isolate(), async_id), + type, + object, + Number::New(env->isolate(), trigger_id), }; - TryCatch try_catch(env()->isolate()); + TryCatch try_catch(env->isolate()); MaybeLocal ret = init_fn->Call( - env()->context(), object(), arraysize(argv), argv); + env->context(), object, arraysize(argv), argv); if (ret.IsEmpty()) { - ClearFatalExceptionHandlers(env()); - FatalException(env()->isolate(), try_catch); + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); } } @@ -620,6 +645,38 @@ Local AsyncWrap::MakeCallback(const Local cb, return rcheck.IsEmpty() ? Local() : ret_v; } + +/* Public C++ embedder API */ + + +async_uid AsyncHooksGetCurrentId(Isolate* isolate) { + return Environment::GetCurrent(isolate)->current_async_id(); +} + + +async_uid AsyncHooksGetTriggerId(Isolate* isolate) { + return Environment::GetCurrent(isolate)->get_init_trigger_id(); +} + + +async_uid EmitAsyncInit(Isolate* isolate, + Local resource, + const char* name, + async_uid trigger_id) { + Environment* env = Environment::GetCurrent(isolate); + async_uid async_id = env->new_async_id(); + + Local type = + String::NewFromUtf8(isolate, name, v8::NewStringType::kInternalized) + .ToLocalChecked(); + AsyncWrap::EmitAsyncInit(env, resource, type, async_id, trigger_id); + return async_id; +} + +void EmitAsyncDestroy(Isolate* isolate, async_uid id) { + PushBackDestroyId(Environment::GetCurrent(isolate), id); +} + } // namespace node NODE_MODULE_CONTEXT_AWARE_BUILTIN(async_wrap, node::AsyncWrap::Initialize) diff --git a/src/async-wrap.h b/src/async-wrap.h index cf81178747..d3676a01c0 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -101,6 +101,15 @@ class AsyncWrap : public BaseObject { static void AsyncReset(const v8::FunctionCallbackInfo& args); static void QueueDestroyId(const v8::FunctionCallbackInfo& args); + static void EmitAsyncInit(Environment* env, + v8::Local object, + v8::Local type, + double id, + double trigger_id); + + static bool EmitBefore(Environment* env, double id); + static bool EmitAfter(Environment* env, double id); + inline ProviderType provider_type() const; inline double get_id() const; diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index a90edc5038..5daef2e1ba 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -597,8 +597,8 @@ bool Agent::StartIoThread(bool wait_for_connect) { FIXED_ONE_BYTE_STRING(isolate, "internalMessage"), message }; - MakeCallback(parent_env_, process_object.As(), emit_fn.As(), - arraysize(argv), argv); + MakeCallback(parent_env_->isolate(), process_object, emit_fn.As(), + arraysize(argv), argv, 0, 0); return true; } diff --git a/src/node.cc b/src/node.cc index 2c323be9f2..8972dd6135 100644 --- a/src/node.cc +++ b/src/node.cc @@ -348,7 +348,12 @@ static void CheckImmediate(uv_check_t* handle) { Environment* env = Environment::from_immediate_check_handle(handle); HandleScope scope(env->isolate()); Context::Scope context_scope(env->context()); - MakeCallback(env, env->process_object(), env->immediate_callback_string()); + MakeCallback(env->isolate(), + env->process_object(), + env->immediate_callback_string(), + 0, + nullptr, + 0, 0).ToLocalChecked(); } @@ -1281,18 +1286,20 @@ void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) { } -Local MakeCallback(Environment* env, - Local recv, - const Local callback, - int argc, - Local argv[]) { +MaybeLocal MakeCallback(Environment* env, + Local recv, + const Local callback, + int argc, + Local argv[], + double async_id, + double trigger_id) { // If you hit this assertion, you forgot to enter the v8::Context first. CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); - Local object, domain; - bool has_domain = false; + Local object; Environment::AsyncCallbackScope callback_scope(env); + bool disposed_domain = false; if (recv->IsObject()) { object = recv.As(); @@ -1300,51 +1307,38 @@ Local MakeCallback(Environment* env, if (env->using_domains()) { CHECK(recv->IsObject()); - Local domain_v = object->Get(env->domain_string()); - has_domain = domain_v->IsObject(); - if (has_domain) { - domain = domain_v.As(); - if (domain->Get(env->disposed_string())->IsTrue()) - return Undefined(env->isolate()); - } + disposed_domain = DomainEnter(env, object); + if (disposed_domain) return Undefined(env->isolate()); } - if (has_domain) { - Local enter_v = domain->Get(env->enter_string()); - if (enter_v->IsFunction()) { - if (enter_v.As()->Call(domain, 0, nullptr).IsEmpty()) { - FatalError("node::MakeCallback", - "domain enter callback threw, please report this"); - } - } - } + MaybeLocal ret; - // 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); + { + AsyncHooks::ExecScope exec_scope(env, async_id, trigger_id); - Local ret = callback->Call(recv, argc, argv); + if (async_id != 0) { + if (!AsyncWrap::EmitBefore(env, async_id)) return Local(); + } - if (ret.IsEmpty()) { - // NOTE: For backwards compatibility with public API we return Undefined() - // if the top level call threw. - return callback_scope.in_makecallback() ? - ret : Undefined(env->isolate()).As(); - } + ret = callback->Call(env->context(), recv, argc, argv); - exec_scope.Dispose(); + if (ret.IsEmpty()) { + // NOTE: For backwards compatibility with public API we return Undefined() + // if the top level call threw. + return callback_scope.in_makecallback() ? + ret : Undefined(env->isolate()); + } - if (has_domain) { - Local exit_v = domain->Get(env->exit_string()); - if (exit_v->IsFunction()) { - if (exit_v.As()->Call(domain, 0, nullptr).IsEmpty()) { - FatalError("node::MakeCallback", - "domain exit callback threw, please report this"); - } + if (async_id != 0) { + if (!AsyncWrap::EmitAfter(env, async_id)) return Local(); } } + if (env->using_domains()) { + disposed_domain = DomainExit(env, object); + if (disposed_domain) return Undefined(env->isolate()); + } + if (callback_scope.in_makecallback()) { return ret; } @@ -1357,8 +1351,8 @@ Local MakeCallback(Environment* env, // 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); + CHECK_EQ(env->current_async_id(), async_id); + CHECK_EQ(env->trigger_id(), trigger_id); Local process = env->process_object(); @@ -1375,70 +1369,96 @@ Local MakeCallback(Environment* env, } -Local MakeCallback(Environment* env, - 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); +// Public MakeCallback()s + + +MaybeLocal MakeCallback(Isolate* isolate, + Local recv, + const char* method, + int argc, + Local argv[], + async_uid async_id, + async_uid trigger_id) { + Local method_string = + String::NewFromUtf8(isolate, method, v8::NewStringType::kNormal) + .ToLocalChecked(); + return MakeCallback(isolate, recv, method_string, argc, argv, + async_id, trigger_id); } -Local MakeCallback(Environment* env, - Local recv, - const char* method, - int argc, - Local argv[]) { - Local method_string = OneByteString(env->isolate(), method); - return MakeCallback(env, recv, method_string, argc, argv); +MaybeLocal MakeCallback(Isolate* isolate, + Local recv, + Local symbol, + int argc, + Local argv[], + async_uid async_id, + async_uid trigger_id) { + Local callback_v = recv->Get(symbol); + if (callback_v.IsEmpty()) return Local(); + if (!callback_v->IsFunction()) return Local(); + Local callback = callback_v.As(); + return MakeCallback(isolate, recv, callback, argc, argv, + async_id, trigger_id); } +MaybeLocal MakeCallback(Isolate* isolate, + Local recv, + Local callback, + int argc, + Local argv[], + async_uid async_id, + async_uid trigger_id) { + // Observe the following two subtleties: + // + // 1. The environment is retrieved from the callback function's context. + // 2. The context to enter is retrieved from the environment. + // + // Because of the AssignToContext() call in src/node_contextify.cc, + // the two contexts need not be the same. + Environment* env = Environment::GetCurrent(callback->CreationContext()); + Context::Scope context_scope(env->context()); + return MakeCallback(env, recv.As(), callback, argc, argv, + async_id, trigger_id); +} + + +// Legacy MakeCallback()s + Local MakeCallback(Isolate* isolate, Local recv, const char* method, int argc, - Local argv[]) { + Local* argv) { EscapableHandleScope handle_scope(isolate); - Local method_string = OneByteString(isolate, method); return handle_scope.Escape( - MakeCallback(isolate, recv, method_string, argc, argv)); + MakeCallback(isolate, recv, method, argc, argv, 0, 0) + .FromMaybe(Local())); } Local MakeCallback(Isolate* isolate, - Local recv, - Local symbol, - int argc, - Local argv[]) { + Local recv, + Local symbol, + int argc, + Local* argv) { EscapableHandleScope handle_scope(isolate); - Local callback_v = recv->Get(symbol); - if (callback_v.IsEmpty()) return Local(); - if (!callback_v->IsFunction()) return Local(); - Local callback = callback_v.As(); - return handle_scope.Escape(MakeCallback(isolate, recv, callback, argc, argv)); + return handle_scope.Escape( + MakeCallback(isolate, recv, symbol, argc, argv, 0, 0) + .FromMaybe(Local())); } Local MakeCallback(Isolate* isolate, - Local recv, - Local callback, - int argc, - Local argv[]) { - // Observe the following two subtleties: - // - // 1. The environment is retrieved from the callback function's context. - // 2. The context to enter is retrieved from the environment. - // - // Because of the AssignToContext() call in src/node_contextify.cc, - // the two contexts need not be the same. + Local recv, + Local callback, + int argc, + Local* argv) { EscapableHandleScope handle_scope(isolate); - Environment* env = Environment::GetCurrent(callback->CreationContext()); - Context::Scope context_scope(env->context()); return handle_scope.Escape( - MakeCallback(env, recv.As(), callback, argc, argv)); + MakeCallback(isolate, recv, callback, argc, argv, 0, 0) + .FromMaybe(Local())); } @@ -4382,7 +4402,9 @@ void EmitBeforeExit(Environment* env) { FIXED_ONE_BYTE_STRING(env->isolate(), "beforeExit"), process_object->Get(exit_code)->ToInteger(env->isolate()) }; - MakeCallback(env, process_object, "emit", arraysize(args), args); + MakeCallback(env->isolate(), + process_object, "emit", arraysize(args), args, + 0, 0).ToLocalChecked(); } @@ -4401,7 +4423,9 @@ int EmitExit(Environment* env) { Integer::New(env->isolate(), code) }; - MakeCallback(env, process_object, "emit", arraysize(args), args); + MakeCallback(env->isolate(), + process_object, "emit", arraysize(args), args, + 0, 0).ToLocalChecked(); // Reload exit code, it may be changed by `emit('exit')` return process_object->Get(exitCode)->Int32Value(); diff --git a/src/node.h b/src/node.h index 7ed4521e87..329a3c623b 100644 --- a/src/node.h +++ b/src/node.h @@ -142,14 +142,10 @@ inline v8::Local UVException(int errorno, } /* - * MakeCallback doesn't have a HandleScope. That means the callers scope - * will retain ownership of created handles from MakeCallback and related. - * There is by default a wrapping HandleScope before uv_run, if the caller - * doesn't have a HandleScope on the stack the global will take ownership - * which won't be reaped until the uv loop exits. + * These methods need to be called in a HandleScope. * - * If a uv callback is fired, and there is no enclosing HandleScope in the - * cb, you will appear to leak 4-bytes for every invocation. Take heed. + * It is preferred that you use the `MakeCallback` overloads taking + * `async_uid` arguments. */ NODE_EXTERN v8::Local MakeCallback( @@ -521,12 +517,135 @@ typedef void (*promise_hook_func) (v8::PromiseHookType type, v8::Local parent, void* arg); +typedef double async_uid; + /* Registers an additional v8::PromiseHook wrapper. This API exists because V8 * itself supports only a single PromiseHook. */ NODE_EXTERN void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg); +/* Returns the id of the current execution context. If the return value is + * zero then no execution has been set. This will happen if the user handles + * I/O from native code. */ +NODE_EXTERN async_uid AsyncHooksGetCurrentId(v8::Isolate* isolate); + +/* Return same value as async_hooks.triggerId(); */ +NODE_EXTERN async_uid AsyncHooksGetTriggerId(v8::Isolate* isolate); + +/* If the native API doesn't inherit from the helper class then the callbacks + * must be triggered manually. This triggers the init() callback. The return + * value is the uid assigned to the resource. + * + * The `trigger_id` parameter should correspond to the resource which is + * creating the new resource, which will usually be the return value of + * `AsyncHooksGetTriggerId()`. */ +NODE_EXTERN async_uid EmitAsyncInit(v8::Isolate* isolate, + v8::Local resource, + const char* name, + async_uid trigger_id); + +/* Emit the destroy() callback. */ +NODE_EXTERN void EmitAsyncDestroy(v8::Isolate* isolate, async_uid id); + +/* An API specific to emit before/after callbacks is unnecessary because + * MakeCallback will automatically call them for you. + * + * These methods may create handles on their own, so run them inside a + * HandleScope. + * + * `asyncId` and `triggerId` should correspond to the values returned by + * `EmitAsyncInit()` and `AsyncHooksGetTriggerId()`, respectively, when the + * invoking resource was created. If these values are unknown, 0 can be passed. + * */ +NODE_EXTERN +v8::MaybeLocal MakeCallback(v8::Isolate* isolate, + v8::Local recv, + v8::Local callback, + int argc, + v8::Local* argv, + async_uid asyncId, + async_uid triggerId); +NODE_EXTERN +v8::MaybeLocal MakeCallback(v8::Isolate* isolate, + v8::Local recv, + const char* method, + int argc, + v8::Local* argv, + async_uid asyncId, + async_uid triggerId); +NODE_EXTERN +v8::MaybeLocal MakeCallback(v8::Isolate* isolate, + v8::Local recv, + v8::Local symbol, + int argc, + v8::Local* argv, + async_uid asyncId, + async_uid triggerId); + +/* Helper class users can optionally inherit from. If + * `AsyncResource::MakeCallback()` is used, then all four callbacks will be + * called automatically. */ +class AsyncResource { + public: + AsyncResource(v8::Isolate* isolate, + v8::Local resource, + const char* name, + async_uid trigger_id = -1) + : isolate_(isolate), + resource_(isolate, resource), + trigger_id_(trigger_id) { + if (trigger_id_ == -1) + trigger_id_ = AsyncHooksGetTriggerId(isolate); + + uid_ = EmitAsyncInit(isolate, resource, name, trigger_id_); + } + + ~AsyncResource() { + EmitAsyncDestroy(isolate_, uid_); + } + + v8::MaybeLocal MakeCallback( + v8::Local callback, + int argc, + v8::Local* argv) { + return node::MakeCallback(isolate_, get_resource(), + callback, argc, argv, + uid_, trigger_id_); + } + + v8::MaybeLocal MakeCallback( + const char* method, + int argc, + v8::Local* argv) { + return node::MakeCallback(isolate_, get_resource(), + method, argc, argv, + uid_, trigger_id_); + } + + v8::MaybeLocal MakeCallback( + v8::Local symbol, + int argc, + v8::Local* argv) { + return node::MakeCallback(isolate_, get_resource(), + symbol, argc, argv, + uid_, trigger_id_); + } + + v8::Local get_resource() { + return resource_.Get(isolate_); + } + + async_uid get_uid() const { + return uid_; + } + private: + v8::Isolate* isolate_; + v8::Persistent resource_; + async_uid uid_; + async_uid trigger_id_; +}; + } // namespace node #endif // SRC_NODE_H_ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 68613ab565..ef95025229 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1237,11 +1237,13 @@ int SecureContext::TicketKeyCallback(SSL* ssl, kTicketPartSize).ToLocalChecked(), Boolean::New(env->isolate(), enc != 0) }; - Local ret = node::MakeCallback(env, + + Local ret = node::MakeCallback(env->isolate(), sc->object(), env->ticketkeycallback_string(), arraysize(argv), - argv); + argv, + 0, 0).ToLocalChecked(); Local arr = ret.As(); int r = arr->Get(kTicketKeyReturnIndex)->Int32Value(); diff --git a/src/node_internals.h b/src/node_internals.h index e07cb9d6d3..a08ab45aff 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -94,27 +94,6 @@ inline v8::Local PersistentToLocal( v8::Isolate* isolate, const v8::Persistent& persistent); -// Call with valid HandleScope and while inside Context scope. -v8::Local MakeCallback(Environment* env, - v8::Local recv, - const char* method, - int argc = 0, - v8::Local* argv = nullptr); - -// Call with valid HandleScope and while inside Context scope. -v8::Local MakeCallback(Environment* env, - v8::Local recv, - v8::Local symbol, - int argc = 0, - v8::Local* argv = nullptr); - -// Call with valid HandleScope and while inside Context scope. -v8::Local MakeCallback(Environment* env, - v8::Local recv, - v8::Local callback, - int argc = 0, - v8::Local* argv = nullptr); - // Convert a struct sockaddr to a { address: '1.2.3.4', port: 1234 } JS object. // Sets address and port properties on the info object and returns it. // If |info| is omitted, a new object is returned.