From 702eeb2e290ed4a45d61f5ecf9cfc2628bfc582c Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Thu, 22 Jun 2023 16:41:29 -0700 Subject: [PATCH] lib: rewrite AsyncLocalStorage without async_hooks --- configure.py | 8 + lib/async_hooks.js | 125 ++------- .../async_local_storage/async_hooks.js | 117 ++++++++ lib/internal/async_local_storage/native.js | 63 +++++ lib/internal/process/promises.js | 13 +- lib/internal/process/task_queues.js | 16 ++ lib/internal/timers.js | 27 ++ node.gyp | 3 + node.gypi | 9 + src/api/async_resource.cc | 24 +- src/api/callback.cc | 68 ++++- src/async_context_frame.cc | 261 ++++++++++++++++++ src/async_context_frame.h | 86 ++++++ src/async_wrap-inl.h | 7 + src/async_wrap.cc | 31 ++- src/async_wrap.h | 8 + src/env_properties.h | 1 + src/node.h | 3 + src/node_api.cc | 12 + src/node_binding.cc | 1 + src/node_external_reference.h | 1 + src/node_internals.h | 32 ++- .../test-async-local-storage-thenable.js | 12 + ...-async-local-storage-exit-does-not-leak.js | 21 +- test/parallel/test-bootstrap-modules.js | 1 + 25 files changed, 824 insertions(+), 126 deletions(-) create mode 100644 lib/internal/async_local_storage/async_hooks.js create mode 100644 lib/internal/async_local_storage/native.js create mode 100644 src/async_context_frame.cc create mode 100644 src/async_context_frame.h diff --git a/configure.py b/configure.py index ee08264e91d8a4..8bf465791209ee 100755 --- a/configure.py +++ b/configure.py @@ -660,6 +660,12 @@ default=None, help='do not install the bundled Corepack') +parser.add_argument('--with-native-als', + action='store_true', + dest='with_native_als', + default=None, + help='use native AsyncLocalStorage') + # Dummy option for backwards compatibility parser.add_argument('--without-report', action='store_true', @@ -1237,6 +1243,8 @@ def configure_node(o): o['default_configuration'] = 'Debug' if options.debug else 'Release' o['variables']['error_on_warn'] = b(options.error_on_warn) + o['variables']['node_use_native_als'] = b(options.with_native_als) + host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc() target_arch = options.dest_cpu or host_arch # ia32 is preferred by the build tools (GYP) over x86 even if we prefer the latter diff --git a/lib/async_hooks.js b/lib/async_hooks.js index b4dd54022d55b1..b5e86a39d20189 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -9,7 +9,6 @@ const { FunctionPrototypeBind, NumberIsSafeInteger, ObjectDefineProperties, - ObjectIs, ReflectApply, Symbol, ObjectFreeze, @@ -30,6 +29,9 @@ const { } = require('internal/validators'); const internal_async_hooks = require('internal/async_hooks'); +const { AsyncContextFrame } = internalBinding('async_context_frame'); +const hasAsyncContextFrame = typeof AsyncContextFrame === 'function'; + // Get functions // For userland AsyncResources, make sure to emit a destroy event when the // resource gets gced. @@ -158,6 +160,7 @@ function createHook(fns) { // Embedder API // const destroyedSymbol = Symbol('destroyed'); +const contextFrameSymbol = Symbol('context_frame'); class AsyncResource { constructor(type, opts = kEmptyObject) { @@ -177,6 +180,8 @@ class AsyncResource { throw new ERR_INVALID_ASYNC_ID('triggerAsyncId', triggerAsyncId); } + this[contextFrameSymbol] = hasAsyncContextFrame && AsyncContextFrame.current(); + const asyncId = newAsyncId(); this[async_id_symbol] = asyncId; this[trigger_async_id_symbol] = triggerAsyncId; @@ -201,12 +206,17 @@ class AsyncResource { const asyncId = this[async_id_symbol]; emitBefore(asyncId, this[trigger_async_id_symbol], this); + const contextFrame = this[contextFrameSymbol]; + let prior; + if (hasAsyncContextFrame) { + prior = AsyncContextFrame.exchange(contextFrame); + } try { - const ret = - ReflectApply(fn, thisArg, args); - - return ret; + return ReflectApply(fn, thisArg, args); } finally { + if (hasAsyncContextFrame) { + AsyncContextFrame.exchange(prior); + } if (hasAsyncIdStack()) emitAfter(asyncId); } @@ -270,110 +280,15 @@ class AsyncResource { } } -const storageList = []; -const storageHook = createHook({ - init(asyncId, type, triggerAsyncId, resource) { - const currentResource = executionAsyncResource(); - // Value of currentResource is always a non null object - for (let i = 0; i < storageList.length; ++i) { - storageList[i]._propagate(resource, currentResource, type); - } - }, -}); - -class AsyncLocalStorage { - constructor() { - this.kResourceStore = Symbol('kResourceStore'); - this.enabled = false; - } - - static bind(fn) { - return AsyncResource.bind(fn); - } - - static snapshot() { - return AsyncLocalStorage.bind((cb, ...args) => cb(...args)); - } - - disable() { - if (this.enabled) { - this.enabled = false; - // If this.enabled, the instance must be in storageList - ArrayPrototypeSplice(storageList, - ArrayPrototypeIndexOf(storageList, this), 1); - if (storageList.length === 0) { - storageHook.disable(); - } - } - } - - _enable() { - if (!this.enabled) { - this.enabled = true; - ArrayPrototypePush(storageList, this); - storageHook.enable(); - } - } - - // Propagate the context from a parent resource to a child one - _propagate(resource, triggerResource, type) { - const store = triggerResource[this.kResourceStore]; - if (this.enabled) { - resource[this.kResourceStore] = store; - } - } - - enterWith(store) { - this._enable(); - const resource = executionAsyncResource(); - resource[this.kResourceStore] = store; - } - - run(store, callback, ...args) { - // Avoid creation of an AsyncResource if store is already active - if (ObjectIs(store, this.getStore())) { - return ReflectApply(callback, null, args); - } - - this._enable(); - - const resource = executionAsyncResource(); - const oldStore = resource[this.kResourceStore]; - - resource[this.kResourceStore] = store; - - try { - return ReflectApply(callback, null, args); - } finally { - resource[this.kResourceStore] = oldStore; - } - } - - exit(callback, ...args) { - if (!this.enabled) { - return ReflectApply(callback, null, args); - } - this.disable(); - try { - return ReflectApply(callback, null, args); - } finally { - this._enable(); - } - } - - getStore() { - if (this.enabled) { - const resource = executionAsyncResource(); - return resource[this.kResourceStore]; - } - } -} - // Placing all exports down here because the exported classes won't export // otherwise. module.exports = { // Public API - AsyncLocalStorage, + get AsyncLocalStorage() { + return hasAsyncContextFrame ? + require('internal/async_local_storage/native') : + require('internal/async_local_storage/async_hooks'); + }, createHook, executionAsyncId, triggerAsyncId, diff --git a/lib/internal/async_local_storage/async_hooks.js b/lib/internal/async_local_storage/async_hooks.js new file mode 100644 index 00000000000000..519dd37e6156c5 --- /dev/null +++ b/lib/internal/async_local_storage/async_hooks.js @@ -0,0 +1,117 @@ +'use strict'; + +const { + ArrayPrototypeIndexOf, + ArrayPrototypePush, + ArrayPrototypeSplice, + ObjectIs, + ReflectApply, + Symbol, +} = primordials; + +const { + AsyncResource, + createHook, + executionAsyncResource, +} = require('async_hooks'); + +const storageList = []; +const storageHook = createHook({ + init(asyncId, type, triggerAsyncId, resource) { + const currentResource = executionAsyncResource(); + // Value of currentResource is always a non null object + for (let i = 0; i < storageList.length; ++i) { + storageList[i]._propagate(resource, currentResource, type); + } + }, +}); + +class AsyncLocalStorage { + constructor() { + this.kResourceStore = Symbol('kResourceStore'); + this.enabled = false; + } + + static bind(fn) { + return AsyncResource.bind(fn); + } + + static snapshot() { + return AsyncLocalStorage.bind((cb, ...args) => cb(...args)); + } + + disable() { + if (this.enabled) { + this.enabled = false; + // If this.enabled, the instance must be in storageList + const index = ArrayPrototypeIndexOf(storageList, this); + ArrayPrototypeSplice(storageList, index, 1); + if (storageList.length === 0) { + storageHook.disable(); + } + } + } + + _enable() { + if (!this.enabled) { + this.enabled = true; + ArrayPrototypePush(storageList, this); + storageHook.enable(); + } + } + + // Propagate the context from a parent resource to a child one + _propagate(resource, triggerResource, type) { + const store = triggerResource[this.kResourceStore]; + if (this.enabled) { + resource[this.kResourceStore] = store; + } + } + + enterWith(store) { + this._enable(); + const resource = executionAsyncResource(); + resource[this.kResourceStore] = store; + } + + run(store, callback, ...args) { + // Avoid creation of an AsyncResource if store is already active + if (ObjectIs(store, this.getStore())) { + return ReflectApply(callback, null, args); + } + + this._enable(); + + const resource = executionAsyncResource(); + const oldStore = resource[this.kResourceStore]; + + resource[this.kResourceStore] = store; + + try { + return ReflectApply(callback, null, args); + } finally { + resource[this.kResourceStore] = oldStore; + } + } + + exit(callback, ...args) { + if (!this.enabled) { + return ReflectApply(callback, null, args); + } + this.disable(); + try { + return ReflectApply(callback, null, args); + } finally { + this._enable(); + } + } + + getStore() { + if (this.enabled) { + const resource = executionAsyncResource(); + return resource[this.kResourceStore]; + } + } +} + +module.exports = AsyncLocalStorage; diff --git a/lib/internal/async_local_storage/native.js b/lib/internal/async_local_storage/native.js new file mode 100644 index 00000000000000..ca1ca9db0b4fd9 --- /dev/null +++ b/lib/internal/async_local_storage/native.js @@ -0,0 +1,63 @@ +'use strict'; + +const { + FunctionPrototypeBind, + ReflectApply, +} = primordials; + +const { validateFunction } = require('internal/validators'); + +const { AsyncContextFrame } = internalBinding('async_context_frame'); + +class AsyncLocalStorage { + static bind(fn) { + validateFunction(fn, 'fn'); + const run = this.snapshot(); + return function bound(...args) { + return run.call(this, fn, ...args); + }; + } + + static snapshot() { + const frame = AsyncContextFrame.current(); + return function runSnapshot(fn, ...args) { + const bound = FunctionPrototypeBind(fn, this); + const prior = AsyncContextFrame.exchange(frame); + try { + return ReflectApply(bound, undefined, args); + } finally { + AsyncContextFrame.exchange(prior); + } + }; + } + + disable() { + AsyncContextFrame.disable(this); + } + + enterWith(data) { + const frame = new AsyncContextFrame(this, data); + AsyncContextFrame.exchange(frame); + } + + run(data, fn, ...args) { + const frame = new AsyncContextFrame(this, data); + const prior = AsyncContextFrame.exchange(frame); + + try { + return ReflectApply(fn, undefined, args); + } finally { + AsyncContextFrame.exchange(prior); + } + } + + exit(fn, ...args) { + return ReflectApply(this.run, this, [undefined, fn, ...args]); + } + + getStore() { + return AsyncContextFrame.current()?.get(this); + } +} + +module.exports = AsyncLocalStorage; diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index d80ce1ef764a00..a1151f171cf19a 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -38,6 +38,9 @@ const { } = require('internal/async_hooks'); const { isErrorStackTraceLimitWritable } = require('internal/errors'); +const { AsyncContextFrame } = internalBinding('async_context_frame'); +const hasAsyncContextFrame = typeof AsyncContextFrame === 'function'; + // *Must* match Environment::TickInfo::Fields in src/env.h. const kHasRejectionToWarn = 1; @@ -156,6 +159,7 @@ function unhandledRejection(promise, reason) { warned: false, domain: process.domain, emit, + contextFrame: hasAsyncContextFrame && AsyncContextFrame.current(), }); // This causes the promise to be referenced at least for one tick. ArrayPrototypePush(pendingUnhandledRejections, promise); @@ -236,7 +240,7 @@ function processPromiseRejections() { continue; } promiseInfo.warned = true; - const { reason, uid, emit } = promiseInfo; + const { reason, uid, emit, contextFrame } = promiseInfo; let needPop = true; const { @@ -253,6 +257,10 @@ function processPromiseRejections() { promise, ); } + let priorContextFrame; + if (hasAsyncContextFrame) { + priorContextFrame = AsyncContextFrame.exchange(contextFrame); + } try { switch (unhandledRejectionsMode) { case kStrictUnhandledRejections: { @@ -301,6 +309,9 @@ function processPromiseRejections() { } } } finally { + if (hasAsyncContextFrame) { + AsyncContextFrame.exchange(priorContextFrame); + } if (needPop) { if (typeof promiseAsyncId !== 'undefined') { popAsyncContext(promiseAsyncId); diff --git a/lib/internal/process/task_queues.js b/lib/internal/process/task_queues.js index bcb5eef841dd00..cce2d7e4d47f77 100644 --- a/lib/internal/process/task_queues.js +++ b/lib/internal/process/task_queues.js @@ -3,6 +3,7 @@ const { Array, FunctionPrototypeBind, + Symbol, } = primordials; const { @@ -41,6 +42,10 @@ const { const { AsyncResource } = require('async_hooks'); +const { AsyncContextFrame } = internalBinding('async_context_frame'); +const hasAsyncContextFrame = typeof AsyncContextFrame === 'function'; +const async_context_frame = Symbol('asyncContextFrame'); + // *Must* match Environment::TickInfo::Fields in src/env.h. const kHasTickScheduled = 0; @@ -68,6 +73,12 @@ function processTicksAndRejections() { let tock; do { while ((tock = queue.shift()) !== null) { + let priorContextFrame; + if (hasAsyncContextFrame) { + priorContextFrame = + AsyncContextFrame.exchange(tock[async_context_frame]); + } + const asyncId = tock[async_id_symbol]; emitBefore(asyncId, tock[trigger_async_id_symbol], tock); @@ -91,6 +102,10 @@ function processTicksAndRejections() { } emitAfter(asyncId); + + if (hasAsyncContextFrame) { + AsyncContextFrame.exchange(priorContextFrame); + } } runMicrotasks(); } while (!queue.isEmpty() || processPromiseRejections()); @@ -125,6 +140,7 @@ function nextTick(callback) { const tickObject = { [async_id_symbol]: asyncId, [trigger_async_id_symbol]: triggerAsyncId, + [async_context_frame]: hasAsyncContextFrame && AsyncContextFrame.current(), callback, args, }; diff --git a/lib/internal/timers.js b/lib/internal/timers.js index aa7d3ec69f2189..e287ffc9927399 100644 --- a/lib/internal/timers.js +++ b/lib/internal/timers.js @@ -121,6 +121,10 @@ let debug = require('internal/util/debuglog').debuglog('timer', (fn) => { debug = fn; }); +const { AsyncContextFrame } = internalBinding('async_context_frame'); +const hasAsyncContextFrame = typeof AsyncContextFrame === 'function'; +const async_context_frame = Symbol('asyncContextFrame'); + // *Must* match Environment::ImmediateInfo::Fields in src/env.h. const kCount = 0; const kRefCount = 1; @@ -156,6 +160,9 @@ function initAsyncResource(resource, type) { const asyncId = resource[async_id_symbol] = newAsyncId(); const triggerAsyncId = resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId(); + if (hasAsyncContextFrame) { + resource[async_context_frame] = AsyncContextFrame.current(); + } if (initHooksExist()) emitInit(asyncId, type, triggerAsyncId, resource); } @@ -469,6 +476,12 @@ function getTimerCallbacks(runNextTicks) { prevImmediate = immediate; + let priorContextFrame; + if (hasAsyncContextFrame) { + priorContextFrame = + AsyncContextFrame.exchange(immediate[async_context_frame]); + } + const asyncId = immediate[async_id_symbol]; emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate); @@ -488,6 +501,10 @@ function getTimerCallbacks(runNextTicks) { } emitAfter(asyncId); + + if (hasAsyncContextFrame) { + AsyncContextFrame.exchange(priorContextFrame); + } } if (queue === outstandingQueue) @@ -559,6 +576,12 @@ function getTimerCallbacks(runNextTicks) { continue; } + let priorContextFrame; + if (hasAsyncContextFrame) { + priorContextFrame = + AsyncContextFrame.exchange(timer[async_context_frame]); + } + emitBefore(asyncId, timer[trigger_async_id_symbol], timer); let start; @@ -589,6 +612,10 @@ function getTimerCallbacks(runNextTicks) { } emitAfter(asyncId); + + if (hasAsyncContextFrame) { + AsyncContextFrame.exchange(priorContextFrame); + } } // If `L.peek(list)` returned nothing, the list was either empty or we have diff --git a/node.gyp b/node.gyp index db6d7455bf1dbd..93c63a8782b29a 100644 --- a/node.gyp +++ b/node.gyp @@ -29,6 +29,7 @@ 'node_builtin_modules_path%': '', 'linked_module_files': [ ], + 'node_use_native_als%': 'false', # We list the deps/ files out instead of globbing them in js2c.cc since we # only include a subset of all the files under these directories. # The lengths of their file names combined should not exceed the @@ -64,6 +65,7 @@ 'src/api/exceptions.cc', 'src/api/hooks.cc', 'src/api/utils.cc', + 'src/async_context_frame.cc', 'src/async_wrap.cc', 'src/base_object.cc', 'src/cares_wrap.cc', @@ -171,6 +173,7 @@ 'src/aliased_buffer-inl.h', 'src/aliased_struct.h', 'src/aliased_struct-inl.h', + 'src/async_context_frame.h', 'src/async_wrap.h', 'src/async_wrap-inl.h', 'src/base_object.h', diff --git a/node.gypi b/node.gypi index 9138317c62c7cd..585fa94889209c 100644 --- a/node.gypi +++ b/node.gypi @@ -96,6 +96,15 @@ 'NODE_USE_V8_PLATFORM=0', ], }], + [ 'node_use_native_als=="true"', { + 'defines': [ + 'NODE_USE_NATIVE_ALS=1', + ], + }, { + 'defines': [ + 'NODE_USE_NATIVE_ALS=0', + ], + }], [ 'v8_enable_shared_ro_heap==1', { 'defines': ['NODE_V8_SHARED_RO_HEAP',], }], diff --git a/src/api/async_resource.cc b/src/api/async_resource.cc index 3c4fbdadbc462c..f8e74d29944a55 100644 --- a/src/api/async_resource.cc +++ b/src/api/async_resource.cc @@ -1,5 +1,6 @@ #include "node.h" #include "env-inl.h" +#include "async_context_frame.h" namespace node { @@ -16,7 +17,13 @@ AsyncResource::AsyncResource(Isolate* isolate, const char* name, async_id trigger_async_id) : env_(Environment::GetCurrent(isolate)), - resource_(isolate, resource) { +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + resource_(isolate, resource), + context_frame_(env_->isolate(), AsyncContextFrame::current(env_)) +#else + resource_(isolate, resource) +#endif +{ CHECK_NOT_NULL(env_); async_context_ = EmitAsyncInit(isolate, resource, name, trigger_async_id); @@ -29,6 +36,11 @@ AsyncResource::~AsyncResource() { MaybeLocal AsyncResource::MakeCallback(Local callback, int argc, Local* argv) { +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + auto context_frame = context_frame_.Get(env_->isolate()); + AsyncContextFrame::Scope async_context_frame_scope(env_->context(), + context_frame); +#endif return node::MakeCallback(env_->isolate(), get_resource(), callback, argc, argv, async_context_); @@ -37,6 +49,11 @@ MaybeLocal AsyncResource::MakeCallback(Local callback, MaybeLocal AsyncResource::MakeCallback(const char* method, int argc, Local* argv) { +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + auto context_frame = context_frame_.Get(env_->isolate()); + AsyncContextFrame::Scope async_context_frame_scope(env_->context(), + context_frame); +#endif return node::MakeCallback(env_->isolate(), get_resource(), method, argc, argv, async_context_); @@ -45,6 +62,11 @@ MaybeLocal AsyncResource::MakeCallback(const char* method, MaybeLocal AsyncResource::MakeCallback(Local symbol, int argc, Local* argv) { +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + auto context_frame = context_frame_.Get(env_->isolate()); + AsyncContextFrame::Scope async_context_frame_scope(env_->context(), + context_frame); +#endif return node::MakeCallback(env_->isolate(), get_resource(), symbol, argc, argv, async_context_); diff --git a/src/api/callback.cc b/src/api/callback.cc index 3a8bd9155a8545..d7246260857f69 100644 --- a/src/api/callback.cc +++ b/src/api/callback.cc @@ -1,4 +1,7 @@ #include "node.h" +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS +#include "async_context_frame.h" +#endif #include "async_wrap-inl.h" #include "env-inl.h" #include "v8.h" @@ -14,6 +17,7 @@ using v8::Local; using v8::MaybeLocal; using v8::Object; using v8::String; +using v8::Undefined; using v8::Value; CallbackScope::CallbackScope(Isolate* isolate, @@ -42,12 +46,22 @@ InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags) async_wrap->object(), { async_wrap->get_async_id(), async_wrap->get_trigger_async_id() }, +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + flags, + async_wrap->context_frame()) {} +#else flags) {} +#endif InternalCallbackScope::InternalCallbackScope(Environment* env, Local object, const async_context& asyncContext, +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + int flags, + v8::Local context_frame) +#else int flags) +#endif : env_(env), async_context_(asyncContext), object_(object), @@ -76,6 +90,11 @@ InternalCallbackScope::InternalCallbackScope(Environment* env, isolate->SetIdle(false); +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + prior_context_frame_.Reset(env->isolate(), + AsyncContextFrame::exchange(env, context_frame)); +#endif + env->async_hooks()->push_async_context( async_context_.async_id, async_context_.trigger_async_id, object); @@ -117,9 +136,15 @@ void InternalCallbackScope::Close() { AsyncWrap::EmitAfter(env_, async_context_.async_id); } - if (pushed_ids_) + if (pushed_ids_) { env_->async_hooks()->pop_async_context(async_context_.async_id); +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + AsyncContextFrame::exchange(env_, + prior_context_frame_.Get(env_->isolate())); +#endif + } + if (failed_) return; if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) { @@ -173,7 +198,12 @@ MaybeLocal InternalMakeCallback(Environment* env, const Local callback, int argc, Local argv[], +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + async_context asyncContext, + Local context_frame) { +#else async_context asyncContext) { +#endif CHECK(!recv.IsEmpty()); #ifdef DEBUG for (int i = 0; i < argc; i++) @@ -194,7 +224,12 @@ MaybeLocal InternalMakeCallback(Environment* env, async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0; } +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + InternalCallbackScope scope( + env, resource, asyncContext, flags, context_frame); +#else InternalCallbackScope scope(env, resource, asyncContext, flags); +#endif if (scope.Failed()) { return MaybeLocal(); } @@ -271,6 +306,24 @@ MaybeLocal MakeCallback(Isolate* isolate, int argc, Local argv[], async_context asyncContext) { +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + return InternalMakeCallback(isolate, + recv, + callback, + argc, + argv, + asyncContext, + Undefined(isolate)); +} + +MaybeLocal InternalMakeCallback(Isolate* isolate, + Local recv, + Local callback, + int argc, + Local argv[], + async_context asyncContext, + Local context_frame) { +#endif // Observe the following two subtleties: // // 1. The environment is retrieved from the callback function's context. @@ -282,8 +335,12 @@ MaybeLocal MakeCallback(Isolate* isolate, Environment::GetCurrent(callback->GetCreationContext().ToLocalChecked()); CHECK_NOT_NULL(env); Context::Scope context_scope(env->context()); - MaybeLocal ret = - InternalMakeCallback(env, recv, recv, callback, argc, argv, asyncContext); + MaybeLocal ret = InternalMakeCallback( +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + env, recv, recv, callback, argc, argv, asyncContext, context_frame); +#else + env, recv, recv, callback, argc, argv, asyncContext); +#endif if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) { // This is only for legacy compatibility and we may want to look into // removing/adjusting it. @@ -318,7 +375,12 @@ MaybeLocal MakeSyncCallback(Isolate* isolate, // didn't provide any async_context to run in. Install a default context. MaybeLocal ret = InternalMakeCallback(env, env->process_object(), recv, callback, argc, argv, +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + async_context{0, 0}, + AsyncContextFrame::current(env)); +#else async_context{0, 0}); +#endif return ret; } diff --git a/src/async_context_frame.cc b/src/async_context_frame.cc new file mode 100644 index 00000000000000..cdc166290723bd --- /dev/null +++ b/src/async_context_frame.cc @@ -0,0 +1,261 @@ +#include "async_context_frame.h" // NOLINT(build/include_inline) + +#include "env-inl.h" +#include "node_errors.h" +#include "node_external_reference.h" +#include "tracing/traced_value.h" +#include "util-inl.h" + +#include "debug_utils-inl.h" + +#include "v8.h" + +using v8::ArrayBufferView; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::ObjectTemplate; +using v8::Value; + +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + +namespace node { + +// +// Scope helper +// +AsyncContextFrame::Scope::Scope(Environment* env, Local object) + : env_(env) { + auto prior = AsyncContextFrame::exchange(env_, object); + prior_.Reset(env_->isolate(), prior); +} + +AsyncContextFrame::Scope::Scope(Environment* env, AsyncContextFrame* frame) + : Scope(env, frame->object()) {} + +AsyncContextFrame::Scope::Scope(Local context, Local object) + : Scope(Environment::GetCurrent(context), object) {} + +AsyncContextFrame::Scope::Scope(Local context, + AsyncContextFrame* frame) + : Scope(Environment::GetCurrent(context), frame) {} + +AsyncContextFrame::Scope::~Scope() { + auto value = prior_.Get(env_->isolate()); + AsyncContextFrame::exchange(env_, value); +} + +// +// Constructor +// +AsyncContextFrame::AsyncContextFrame(Environment* env, + Local obj, + Local current, + Local key, + Local value) + : BaseObject(env, obj), + parent_(env->isolate(), current), + key_(env->isolate(), key), + value_(env->isolate(), value), + enabled_(true) { + key_.SetWeak(); +} + +Local AsyncContextFrame::current(Environment* env) { + return env->isolate() + ->GetEnteredOrMicrotaskContext() + ->GetContinuationPreservedEmbedderData(); +} + +Local AsyncContextFrame::disable(Environment* env, Local key) { + Isolate* isolate = env->isolate(); + + if (key_ == key) { + enabled_ = false; + return v8::True(isolate); + } + + auto parent = parent_.Get(isolate); + if (parent.IsEmpty() || !AsyncContextFrame::HasInstance(env, parent)) { + return v8::False(isolate); + } + + return Unwrap(parent)->disable(env, key); +} + +// NOTE: It's generally recommended to use AsyncContextFrame::Scope +// but sometimes (such as enterWith) a direct exchange is needed. +Local AsyncContextFrame::exchange(Environment* env, Local value) { + auto prior = current(env); + env->isolate() + ->GetEnteredOrMicrotaskContext() + ->SetContinuationPreservedEmbedderData(value); + return prior; +} + +Local AsyncContextFrame::get(Environment* env, Local key) { + Isolate* isolate = env->isolate(); + + if (key_ == key && enabled_) { + return value_.Get(isolate); + } + + auto parent = parent_.Get(isolate); + if (parent.IsEmpty()) { + return v8::Undefined(isolate); + } + + if (!AsyncContextFrame::HasInstance(env, parent)) { + return v8::Undefined(isolate); + } + + return Unwrap(parent)->get(env, key); +} + +// +// JS Static Methods +// +void AsyncContextFrame::New(const FunctionCallbackInfo& info) { + CHECK(info.IsConstructCall()); + CHECK(info.Length() >= 2); + + Environment* env = Environment::GetCurrent(info); + + auto key = info[0]; + auto value = info[1]; + + // Parent frame is optional. Defaults to current frame. + auto parent = AsyncContextFrame::HasInstance(env, info[2]) + ? Unwrap(info[2])->object() + : current(env).As(); + + new AsyncContextFrame(env, info.This(), parent, key, value); +} + +void AsyncContextFrame::Get(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + AsyncContextFrame* acf = Unwrap(info.This()); + info.GetReturnValue().Set(acf->get(env, info[0])); +} + +void AsyncContextFrame::Disable(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + AsyncContextFrame* acf = Unwrap(info.This()); + info.GetReturnValue().Set(acf->disable(env, info[0])); +} + +void AsyncContextFrame::Current(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + info.GetReturnValue().Set(AsyncContextFrame::current(env)); +} + +void AsyncContextFrame::DisableStatic(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + AsyncContextFrame* acf = Unwrap(current(env)); + info.GetReturnValue().Set(acf->disable(env, info[0])); +} + +void AsyncContextFrame::Exchange(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + info.GetReturnValue().Set(AsyncContextFrame::exchange(env, info[0])); +} + +// +// Class construction infra +// +Local AsyncContextFrame::GetConstructorTemplate( + Environment* env) { + return GetConstructorTemplate(env->isolate_data()); +} + +Local AsyncContextFrame::GetConstructorTemplate( + IsolateData* isolate_data) { + Local tmpl = isolate_data->async_context_frame_ctor_template(); + if (tmpl.IsEmpty()) { + Isolate* isolate = isolate_data->isolate(); + tmpl = NewFunctionTemplate(isolate, New); + tmpl->InstanceTemplate()->SetInternalFieldCount( + BaseObject::kInternalFieldCount); + tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "AsyncContextFrame")); + SetProtoMethodNoSideEffect(isolate, tmpl, "get", Get); + SetProtoMethodNoSideEffect(isolate, tmpl, "disable", Disable); + SetMethod(isolate, tmpl, "current", Current); + SetMethod(isolate, tmpl, "disable", DisableStatic); + SetMethod(isolate, tmpl, "exchange", Exchange); + isolate_data->set_async_context_frame_ctor_template(tmpl); + } + return tmpl; +} + +bool AsyncContextFrame::HasInstance(Environment* env, + v8::Local object) { + return GetConstructorTemplate(env->isolate_data())->HasInstance(object); +} + +BaseObjectPtr AsyncContextFrame::Create( + Environment* env, + Local key, + Local value, + Local current) { + Local obj; + + if (UNLIKELY(!GetConstructorTemplate(env) + ->InstanceTemplate() + ->NewInstance(env->context()) + .ToLocal(&obj))) { + return BaseObjectPtr(); + } + + return MakeBaseObject(env, obj, current, key, value); +} + +void AsyncContextFrame::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(New); + registry->Register(Get); + registry->Register(Disable); + registry->Register(DisableStatic); + registry->Register(Current); + registry->Register(Exchange); +} + +void AsyncContextFrame::CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + + auto t = AsyncContextFrame::GetConstructorTemplate(env); + SetConstructorFunction(context, target, "AsyncContextFrame", t); +} + +void AsyncContextFrame::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("parent", parent_); + tracker->TrackField("key", key_); + tracker->TrackField("value", value_); +} + +} // namespace node + +NODE_BINDING_CONTEXT_AWARE_INTERNAL(async_context_frame, + node::AsyncContextFrame::CreatePerContextProperties) +NODE_BINDING_EXTERNAL_REFERENCE(async_context_frame, + node::AsyncContextFrame::RegisterExternalReferences) + +#else +namespace node { +void EmptyProperties(Local target, Local unused, + Local context, void* priv) {} + +void EmptyExternals(ExternalReferenceRegistry* registry) {} +} // namespace node + +NODE_BINDING_CONTEXT_AWARE_INTERNAL(async_context_frame, node::EmptyProperties) + +NODE_BINDING_EXTERNAL_REFERENCE(async_context_frame, node::EmptyExternals) +#endif // defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS diff --git a/src/async_context_frame.h b/src/async_context_frame.h new file mode 100644 index 00000000000000..1502d224dc0672 --- /dev/null +++ b/src/async_context_frame.h @@ -0,0 +1,86 @@ +#ifndef SRC_ASYNC_CONTEXT_FRAME_H_ +#define SRC_ASYNC_CONTEXT_FRAME_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + +#include "base_object.h" +#include "v8.h" + +#include + +namespace node { + +class ExternalReferenceRegistry; + +class AsyncContextFrame final : public BaseObject { + public: + AsyncContextFrame(Environment* env, + v8::Local object, + v8::Local current, + v8::Local key, + v8::Local value); + + AsyncContextFrame() = delete; + + class Scope { + public: + explicit Scope(Environment* env, v8::Local object); + explicit Scope(Environment* env, AsyncContextFrame* frame); + explicit Scope(v8::Local context, v8::Local object); + explicit Scope(v8::Local context, AsyncContextFrame* frame); + ~Scope(); + + private: + node::Environment* env_; + v8::Global prior_; + }; + + static v8::Local current(Environment* env); + static v8::Local exchange(Environment* env, + v8::Local value); + v8::Local get(Environment* env, v8::Local key); + v8::Local disable(Environment* env, v8::Local key); + + static void New(const v8::FunctionCallbackInfo& args); + static void Get(const v8::FunctionCallbackInfo& args); + static void Disable(const v8::FunctionCallbackInfo& args); + static void DisableStatic(const v8::FunctionCallbackInfo& args); + static void Current(const v8::FunctionCallbackInfo& args); + static void Exchange(const v8::FunctionCallbackInfo& args); + + static v8::Local GetConstructorTemplate( + IsolateData* isolate_data); + inline static v8::Local GetConstructorTemplate( + Environment* env); + static bool HasInstance(Environment* env, v8::Local value); + static BaseObjectPtr Create( + Environment* env, + v8::Local key, + v8::Local value, + v8::Local current = v8::Local()); + + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); + static void CreatePerContextProperties(v8::Local target, + v8::Local unused, + v8::Local context, + void* priv); + + // If this needs memory info, swap the next two lines + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(AsyncContextFrame) + SET_SELF_SIZE(AsyncContextFrame) + + private: + v8::Global parent_; + v8::Global key_; + v8::Global value_; + bool enabled_; +}; + +} // namespace node + +#endif // defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_ASYNC_CONTEXT_FRAME_H_ diff --git a/src/async_wrap-inl.h b/src/async_wrap-inl.h index 30b29c83e0c90c..df026c4b076fd2 100644 --- a/src/async_wrap-inl.h +++ b/src/async_wrap-inl.h @@ -50,6 +50,13 @@ inline double AsyncWrap::get_trigger_async_id() const { } +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS +inline v8::Local AsyncWrap::context_frame() const { + return context_frame_.Get(env()->isolate()); +} +#endif + + inline v8::MaybeLocal AsyncWrap::MakeCallback( const v8::Local symbol, int argc, diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 42cddc52aed285..64ab8c426bf255 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -27,6 +27,10 @@ #include "tracing/traced_value.h" #include "util-inl.h" +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS +#include "async_context_frame.h" +#endif + #include "v8.h" using v8::Context; @@ -501,7 +505,13 @@ AsyncWrap::AsyncWrap(Environment* env, } AsyncWrap::AsyncWrap(Environment* env, Local object) - : BaseObject(env, object) { +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + : BaseObject(env, object), + context_frame_(env->isolate(), AsyncContextFrame::current(env)) +#else + : BaseObject(env, object) +#endif +{ } // This method is necessary to work around one specific problem: @@ -625,6 +635,10 @@ void AsyncWrap::AsyncReset(Local resource, double execution_async_id, UNREACHABLE(); } +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + context_frame_.Reset(env()->isolate(), AsyncContextFrame::current(env())); +#endif + if (silent) return; EmitAsyncInit(env(), resource, @@ -668,8 +682,19 @@ MaybeLocal AsyncWrap::MakeCallback(const Local cb, ProviderType provider = provider_type(); async_context context { get_async_id(), get_trigger_async_id() }; - MaybeLocal ret = InternalMakeCallback( - env(), object(), object(), cb, argc, argv, context); + MaybeLocal ret = + InternalMakeCallback(env(), + object(), + object(), + cb, + argc, + argv, +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + context, + context_frame_.Get(env()->isolate())); +#else + context); +#endif // This is a static call with cached values because the `this` object may // no longer be alive at this point. diff --git a/src/async_wrap.h b/src/async_wrap.h index 01e981aa671a23..98484333266fe2 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -195,6 +195,10 @@ class AsyncWrap : public BaseObject { inline double get_async_id() const; inline double get_trigger_async_id() const; +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + inline v8::Local context_frame() const; +#endif + void AsyncReset(v8::Local resource, double execution_async_id = kInvalidAsyncId, bool silent = false); @@ -247,6 +251,10 @@ class AsyncWrap : public BaseObject { // Because the values may be Reset(), cannot be made const. double async_id_ = kInvalidAsyncId; double trigger_async_id_ = kInvalidAsyncId; + +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + v8::Global context_frame_; +#endif }; } // namespace node diff --git a/src/env_properties.h b/src/env_properties.h index 687422d1a4fb0b..b0ea54b9f1128a 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -334,6 +334,7 @@ V(x_forwarded_string, "x-forwarded-for") #define PER_ISOLATE_TEMPLATE_PROPERTIES(V) \ + V(async_context_frame_ctor_template, v8::FunctionTemplate) \ V(async_wrap_ctor_template, v8::FunctionTemplate) \ V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ V(binding_data_default_template, v8::ObjectTemplate) \ diff --git a/src/node.h b/src/node.h index 846ec413f8e1fc..08584fcca5e73a 100644 --- a/src/node.h +++ b/src/node.h @@ -1469,6 +1469,9 @@ class NODE_EXTERN AsyncResource { Environment* env_; v8::Global resource_; async_context async_context_; +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + v8::Global context_frame_; +#endif }; #ifndef _WIN32 diff --git a/src/node_api.cc b/src/node_api.cc index 1f1cfb916a7f1e..68a04767333302 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -1,3 +1,4 @@ +#include "async_context_frame.h" #include "async_wrap-inl.h" #include "env-inl.h" #define NAPI_EXPERIMENTAL @@ -542,6 +543,9 @@ class AsyncContext { async_id_ = node_env()->new_async_id(); trigger_async_id_ = node_env()->get_default_trigger_async_id(); resource_.Reset(node_env()->isolate(), resource_object); + context_frame_.Reset( + node_env()->isolate(), + node::AsyncContextFrame::current(node_env())); lost_reference_ = false; if (externally_managed_resource) { resource_.SetWeak( @@ -573,7 +577,12 @@ class AsyncContext { callback, argc, argv, +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + {async_id_, trigger_async_id_}, + context_frame_.Get(node_env()->isolate())); +#else {async_id_, trigger_async_id_}); +#endif } inline napi_callback_scope OpenCallbackScope() { @@ -629,6 +638,9 @@ class AsyncContext { double trigger_async_id_; v8::Global resource_; bool lost_reference_; +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + v8::Global context_frame_; +#endif }; } // end of anonymous namespace diff --git a/src/node_binding.cc b/src/node_binding.cc index 97257d47c61738..fe6c0796792ece 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -27,6 +27,7 @@ // node is built as static library. No need to depend on the // __attribute__((constructor)) like mechanism in GCC. #define NODE_BUILTIN_STANDARD_BINDINGS(V) \ + V(async_context_frame) \ V(async_wrap) \ V(blob) \ V(block_list) \ diff --git a/src/node_external_reference.h b/src/node_external_reference.h index ae37094c8e117e..e9ff2250ed0027 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -81,6 +81,7 @@ class ExternalReferenceRegistry { }; #define EXTERNAL_REFERENCE_BINDING_LIST_BASE(V) \ + V(async_context_frame) \ V(async_wrap) \ V(binding) \ V(blob) \ diff --git a/src/node_internals.h b/src/node_internals.h index 9243344eb788b5..bfbf99e12090ee 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -202,7 +202,21 @@ v8::MaybeLocal InternalMakeCallback( const v8::Local callback, int argc, v8::Local argv[], +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + async_context asyncContext, + v8::Local context_frame); + +v8::MaybeLocal InternalMakeCallback( + v8::Isolate* isolate, + v8::Local recv, + const v8::Local callback, + int argc, + v8::Local argv[], + async_context asyncContext, + v8::Local context_frame); +#else async_context asyncContext); +#endif v8::MaybeLocal MakeSyncCallback(v8::Isolate* isolate, v8::Local recv, @@ -222,10 +236,17 @@ class InternalCallbackScope { // compatibility issues, but it shouldn't.) kSkipTaskQueues = 2 }; - InternalCallbackScope(Environment* env, - v8::Local object, - const async_context& asyncContext, - int flags = kNoFlags); + InternalCallbackScope( + Environment* env, + v8::Local object, + const async_context& asyncContext, +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + int flags = kNoFlags, + v8::Local context_frame = v8::Local()); +#else + int flags = kNoFlags); +#endif + // Utility that can be used by AsyncWrap classes. explicit InternalCallbackScope(AsyncWrap* async_wrap, int flags = 0); ~InternalCallbackScope(); @@ -243,6 +264,9 @@ class InternalCallbackScope { bool failed_ = false; bool pushed_ids_ = false; bool closed_ = false; +#if defined(NODE_USE_NATIVE_ALS) && NODE_USE_NATIVE_ALS + v8::Global prior_context_frame_; +#endif }; class DebugSealHandleScope { diff --git a/test/async-hooks/test-async-local-storage-thenable.js b/test/async-hooks/test-async-local-storage-thenable.js index 3dc6841ac9838e..a1df07dbb6189a 100644 --- a/test/async-hooks/test-async-local-storage-thenable.js +++ b/test/async-hooks/test-async-local-storage-thenable.js @@ -1,3 +1,4 @@ +// Flags: --expose-internals 'use strict'; const common = require('../common'); @@ -5,6 +6,17 @@ const common = require('../common'); const assert = require('assert'); const { AsyncLocalStorage } = require('async_hooks'); +// TODO(qard): This is known to fail with ContinuationPreservedEmbedderData +// as thenables are not yet supported in V8. A fix should hopefully land soon. +// +// See: https://chromium-review.googlesource.com/c/v8/v8/+/4674242 +const { internalBinding } = require('internal/test/binding'); +const { AsyncContextFrame } = internalBinding('async_context_frame'); +const hasAsyncContextFrame = typeof AsyncContextFrame === 'function'; +if (hasAsyncContextFrame) { + return; +} + // This test verifies that async local storage works with thenables const store = new AsyncLocalStorage(); diff --git a/test/parallel/test-async-local-storage-exit-does-not-leak.js b/test/parallel/test-async-local-storage-exit-does-not-leak.js index 636d80f788b7fb..61a339724008d5 100644 --- a/test/parallel/test-async-local-storage-exit-does-not-leak.js +++ b/test/parallel/test-async-local-storage-exit-does-not-leak.js @@ -5,20 +5,23 @@ const { AsyncLocalStorage } = require('async_hooks'); const als = new AsyncLocalStorage(); -// Make sure _propagate function exists. -assert.ok(typeof als._propagate === 'function'); - -// The als instance should be getting removed from the storageList in -// lib/async_hooks.js when exit(...) is called, therefore when the nested runs -// are called there should be no copy of the als in the storageList to run the -// _propagate method on. -als._propagate = common.mustNotCall('_propagate() should not be called'); +// The _propagate function only exists on the old JavaScript implementation. +if (typeof als._propagate === 'function') { + // The als instance should be getting removed from the storageList in + // lib/async_hooks.js when exit(...) is called, therefore when the nested runs + // are called there should be no copy of the als in the storageList to run the + // _propagate method on. + als._propagate = common.mustNotCall('_propagate() should not be called'); +} const done = common.mustCall(); +const data = true; + function run(count) { if (count === 0) return done(); - als.run({}, () => { + assert.notStrictEqual(als.getStore(), data); + als.run(data, () => { als.exit(run, --count); }); } diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 9abe2dee22c1c7..420fb2755387d7 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -16,6 +16,7 @@ const expectedModules = new Set([ 'NativeModule internal/errors', 'Internal Binding config', 'Internal Binding timers', + 'Internal Binding async_context_frame', 'Internal Binding async_wrap', 'Internal Binding task_queue', 'Internal Binding symbols',