From 4f3d7bb4178ef5742cb6248a413992c3326c3055 Mon Sep 17 00:00:00 2001 From: Nicolai Stange Date: Sun, 29 Nov 2020 03:11:29 +0100 Subject: [PATCH] src: introduce convenience node::MakeSyncCallback() There are situations where one wants to invoke a JS callback's ->Call() from C++ and in particular retain any existing async_context state, but where it's not obvious that a plain ->Call() would be safe at the point in question. Such callsites usually resort to node::MakeCallback(..., async_context{0, 0}), which unconditionally pushes the async_context{0, 0} and takes the required provisions for the ->Call() itself such as triggering the tick after its return, if needed. An example would be the PerformanceObserver invocation from PerformanceEntry::Notify(): this can get called when coming from JS through e.g. perf_hooks.performance.mark() and alike, but perhaps also from nghttp2 (c.f. EmitStatistics() in node_http2.cc). In the former case, a plain ->Call() would be safe and it would be desirable to retain the current async_context so that PerformanceObservers can access it resp. the associated AsyncLocalStorage. However, in the second case the additional provisions taken by node::MakeCallback() might potentially be strictly required. So PerformanceEntry::Notify() bites the bullet and invokes the PerformanceObservers through node::MakeCallback() unconditionally, thereby always rendering any possibly preexisting async_context inaccessible. Introduce the convenience node::MakeSyncCallback() for such usecases, which would basically forward to ->Call() if safe and to node::MakeCallback(..., async_context{0, 0}) otherwise. Co-Authored-By: ZauberNerd PR-URL: https://github.com/nodejs/node/pull/36343 Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Rich Trott --- src/api/callback.cc | 28 ++++++++++++++++++++++++++++ src/node_internals.h | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/api/callback.cc b/src/api/callback.cc index 3d4f91a866ea39..8a0b71cd3626e2 100644 --- a/src/api/callback.cc +++ b/src/api/callback.cc @@ -266,6 +266,34 @@ MaybeLocal MakeCallback(Isolate* isolate, return ret; } +// Use this if you just want to safely invoke some JS callback and +// would like to retain the currently active async_context, if any. +// In case none is available, a fixed default context will be +// installed otherwise. +MaybeLocal MakeSyncCallback(Isolate* isolate, + Local recv, + Local callback, + int argc, + Local argv[]) { + Environment* env = Environment::GetCurrent(callback->CreationContext()); + CHECK_NOT_NULL(env); + if (!env->can_call_into_js()) return Local(); + + Context::Scope context_scope(env->context()); + if (env->async_callback_scope_depth()) { + // There's another MakeCallback() on the stack, piggy back on it. + // In particular, retain the current async_context. + return callback->Call(env->context(), recv, argc, argv); + } + + // This is a toplevel invocation and the caller (intentionally) + // didn't provide any async_context to run in. Install a default context. + MaybeLocal ret = + InternalMakeCallback(env, env->process_object(), recv, callback, argc, argv, + async_context{0, 0}); + return ret; +} + // Legacy MakeCallback()s Local MakeCallback(Isolate* isolate, diff --git a/src/node_internals.h b/src/node_internals.h index f7a1e2d8d62c24..b75092d662dc97 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -200,6 +200,12 @@ v8::MaybeLocal InternalMakeCallback( v8::Local argv[], async_context asyncContext); +v8::MaybeLocal MakeSyncCallback(v8::Isolate* isolate, + v8::Local recv, + v8::Local callback, + int argc, + v8::Local argv[]); + class InternalCallbackScope { public: enum Flags {