mirror of
https://github.com/nodejs/node.git
synced 2025-04-29 14:25:18 +00:00
lib: rewrite AsyncLocalStorage without async_hooks
PR-URL: https://github.com/nodejs/node/pull/48528 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
This commit is contained in:
parent
0c1877a82a
commit
d1229eeca4
@ -38,9 +38,11 @@ function runInAsyncScopes(resourceCount, cb, i = 0) {
|
|||||||
|
|
||||||
function main({ n, resourceCount }) {
|
function main({ n, resourceCount }) {
|
||||||
const store = new AsyncLocalStorage();
|
const store = new AsyncLocalStorage();
|
||||||
|
store.run({}, () => {
|
||||||
runInAsyncScopes(resourceCount, () => {
|
runInAsyncScopes(resourceCount, () => {
|
||||||
bench.start();
|
bench.start();
|
||||||
runBenchmark(store, n);
|
runBenchmark(store, n);
|
||||||
bench.end(n);
|
bench.end(n);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ const { AsyncLocalStorage } = require('async_hooks');
|
|||||||
* - AsyncLocalStorage1.getStore()
|
* - AsyncLocalStorage1.getStore()
|
||||||
*/
|
*/
|
||||||
const bench = common.createBenchmark(main, {
|
const bench = common.createBenchmark(main, {
|
||||||
sotrageCount: [1, 10, 100],
|
storageCount: [1, 10, 100],
|
||||||
n: [1e4],
|
n: [1e4],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ function runStores(stores, value, cb, idx = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function main({ n, sotrageCount }) {
|
function main({ n, storageCount }) {
|
||||||
const stores = new Array(sotrageCount).fill(0).map(() => new AsyncLocalStorage());
|
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
|
||||||
const contextValue = {};
|
const contextValue = {};
|
||||||
|
|
||||||
runStores(stores, contextValue, () => {
|
runStores(stores, contextValue, () => {
|
||||||
|
@ -886,6 +886,21 @@ and `"` are usable.
|
|||||||
It is possible to run code containing inline types by passing
|
It is possible to run code containing inline types by passing
|
||||||
[`--experimental-strip-types`][].
|
[`--experimental-strip-types`][].
|
||||||
|
|
||||||
|
### `--experimental-async-context-frame`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
Enables the use of AsyncLocalStorage backed by AsyncContextFrame rather than
|
||||||
|
the default implementation which relies on async\_hooks. This new model is
|
||||||
|
implemented very differently and so could have differences in how context data
|
||||||
|
flows within the application. As such, it is presently recommended to be sure
|
||||||
|
your application behaviour is unaffected by this change before using it in
|
||||||
|
production.
|
||||||
|
|
||||||
### `--experimental-default-type=type`
|
### `--experimental-default-type=type`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
@ -2893,6 +2908,7 @@ one is included in the list below.
|
|||||||
* `--enable-network-family-autoselection`
|
* `--enable-network-family-autoselection`
|
||||||
* `--enable-source-maps`
|
* `--enable-source-maps`
|
||||||
* `--experimental-abortcontroller`
|
* `--experimental-abortcontroller`
|
||||||
|
* `--experimental-async-context-frame`
|
||||||
* `--experimental-default-type`
|
* `--experimental-default-type`
|
||||||
* `--experimental-detect-module`
|
* `--experimental-detect-module`
|
||||||
* `--experimental-eventsource`
|
* `--experimental-eventsource`
|
||||||
|
@ -10,7 +10,6 @@ const {
|
|||||||
NumberIsSafeInteger,
|
NumberIsSafeInteger,
|
||||||
ObjectDefineProperties,
|
ObjectDefineProperties,
|
||||||
ObjectFreeze,
|
ObjectFreeze,
|
||||||
ObjectIs,
|
|
||||||
ReflectApply,
|
ReflectApply,
|
||||||
Symbol,
|
Symbol,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
@ -30,6 +29,8 @@ const {
|
|||||||
} = require('internal/validators');
|
} = require('internal/validators');
|
||||||
const internal_async_hooks = require('internal/async_hooks');
|
const internal_async_hooks = require('internal/async_hooks');
|
||||||
|
|
||||||
|
const AsyncContextFrame = require('internal/async_context_frame');
|
||||||
|
|
||||||
// Get functions
|
// Get functions
|
||||||
// For userland AsyncResources, make sure to emit a destroy event when the
|
// For userland AsyncResources, make sure to emit a destroy event when the
|
||||||
// resource gets gced.
|
// resource gets gced.
|
||||||
@ -158,6 +159,7 @@ function createHook(fns) {
|
|||||||
// Embedder API //
|
// Embedder API //
|
||||||
|
|
||||||
const destroyedSymbol = Symbol('destroyed');
|
const destroyedSymbol = Symbol('destroyed');
|
||||||
|
const contextFrameSymbol = Symbol('context_frame');
|
||||||
|
|
||||||
class AsyncResource {
|
class AsyncResource {
|
||||||
constructor(type, opts = kEmptyObject) {
|
constructor(type, opts = kEmptyObject) {
|
||||||
@ -177,6 +179,8 @@ class AsyncResource {
|
|||||||
throw new ERR_INVALID_ASYNC_ID('triggerAsyncId', triggerAsyncId);
|
throw new ERR_INVALID_ASYNC_ID('triggerAsyncId', triggerAsyncId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this[contextFrameSymbol] = AsyncContextFrame.current();
|
||||||
|
|
||||||
const asyncId = newAsyncId();
|
const asyncId = newAsyncId();
|
||||||
this[async_id_symbol] = asyncId;
|
this[async_id_symbol] = asyncId;
|
||||||
this[trigger_async_id_symbol] = triggerAsyncId;
|
this[trigger_async_id_symbol] = triggerAsyncId;
|
||||||
@ -201,12 +205,12 @@ class AsyncResource {
|
|||||||
const asyncId = this[async_id_symbol];
|
const asyncId = this[async_id_symbol];
|
||||||
emitBefore(asyncId, this[trigger_async_id_symbol], this);
|
emitBefore(asyncId, this[trigger_async_id_symbol], this);
|
||||||
|
|
||||||
|
const contextFrame = this[contextFrameSymbol];
|
||||||
|
const prior = AsyncContextFrame.exchange(contextFrame);
|
||||||
try {
|
try {
|
||||||
const ret =
|
return ReflectApply(fn, thisArg, args);
|
||||||
ReflectApply(fn, thisArg, args);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
} finally {
|
} finally {
|
||||||
|
AsyncContextFrame.set(prior);
|
||||||
if (hasAsyncIdStack())
|
if (hasAsyncIdStack())
|
||||||
emitAfter(asyncId);
|
emitAfter(asyncId);
|
||||||
}
|
}
|
||||||
@ -270,110 +274,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
|
// Placing all exports down here because the exported classes won't export
|
||||||
// otherwise.
|
// otherwise.
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Public API
|
// Public API
|
||||||
AsyncLocalStorage,
|
get AsyncLocalStorage() {
|
||||||
|
return AsyncContextFrame.enabled ?
|
||||||
|
require('internal/async_local_storage/native') :
|
||||||
|
require('internal/async_local_storage/async_hooks');
|
||||||
|
},
|
||||||
createHook,
|
createHook,
|
||||||
executionAsyncId,
|
executionAsyncId,
|
||||||
triggerAsyncId,
|
triggerAsyncId,
|
||||||
|
50
lib/internal/async_context_frame.js
Normal file
50
lib/internal/async_context_frame.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {
|
||||||
|
getContinuationPreservedEmbedderData,
|
||||||
|
setContinuationPreservedEmbedderData,
|
||||||
|
} = internalBinding('async_context_frame');
|
||||||
|
|
||||||
|
let enabled_;
|
||||||
|
|
||||||
|
class AsyncContextFrame extends Map {
|
||||||
|
constructor(store, data) {
|
||||||
|
super(AsyncContextFrame.current());
|
||||||
|
this.set(store, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get enabled() {
|
||||||
|
enabled_ ??= require('internal/options')
|
||||||
|
.getOptionValue('--experimental-async-context-frame');
|
||||||
|
return enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static current() {
|
||||||
|
if (this.enabled) {
|
||||||
|
return getContinuationPreservedEmbedderData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static set(frame) {
|
||||||
|
if (this.enabled) {
|
||||||
|
setContinuationPreservedEmbedderData(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static exchange(frame) {
|
||||||
|
const prior = this.current();
|
||||||
|
this.set(frame);
|
||||||
|
return prior;
|
||||||
|
}
|
||||||
|
|
||||||
|
static disable(store) {
|
||||||
|
const frame = this.current();
|
||||||
|
frame?.disable(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable(store) {
|
||||||
|
this.delete(store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AsyncContextFrame;
|
117
lib/internal/async_local_storage/async_hooks.js
Normal file
117
lib/internal/async_local_storage/async_hooks.js
Normal file
@ -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;
|
47
lib/internal/async_local_storage/native.js
Normal file
47
lib/internal/async_local_storage/native.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {
|
||||||
|
ReflectApply,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
|
const AsyncContextFrame = require('internal/async_context_frame');
|
||||||
|
const { AsyncResource } = require('async_hooks');
|
||||||
|
|
||||||
|
class AsyncLocalStorage {
|
||||||
|
static bind(fn) {
|
||||||
|
return AsyncResource.bind(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static snapshot() {
|
||||||
|
return AsyncLocalStorage.bind((cb, ...args) => cb(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
AsyncContextFrame.disable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
enterWith(data) {
|
||||||
|
const frame = new AsyncContextFrame(this, data);
|
||||||
|
AsyncContextFrame.set(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
run(data, fn, ...args) {
|
||||||
|
const prior = AsyncContextFrame.current();
|
||||||
|
this.enterWith(data);
|
||||||
|
try {
|
||||||
|
return ReflectApply(fn, null, args);
|
||||||
|
} finally {
|
||||||
|
AsyncContextFrame.set(prior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(fn, ...args) {
|
||||||
|
return this.run(undefined, fn, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStore() {
|
||||||
|
return AsyncContextFrame.current()?.get(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AsyncLocalStorage;
|
@ -38,6 +38,8 @@ const {
|
|||||||
} = require('internal/async_hooks');
|
} = require('internal/async_hooks');
|
||||||
const { isErrorStackTraceLimitWritable } = require('internal/errors');
|
const { isErrorStackTraceLimitWritable } = require('internal/errors');
|
||||||
|
|
||||||
|
const AsyncContextFrame = require('internal/async_context_frame');
|
||||||
|
|
||||||
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
||||||
const kHasRejectionToWarn = 1;
|
const kHasRejectionToWarn = 1;
|
||||||
|
|
||||||
@ -260,6 +262,7 @@ function unhandledRejection(promise, reason) {
|
|||||||
uid: ++lastPromiseId,
|
uid: ++lastPromiseId,
|
||||||
warned: false,
|
warned: false,
|
||||||
domain: process.domain,
|
domain: process.domain,
|
||||||
|
contextFrame: AsyncContextFrame.current(),
|
||||||
});
|
});
|
||||||
setHasRejectionToWarn(true);
|
setHasRejectionToWarn(true);
|
||||||
}
|
}
|
||||||
@ -466,9 +469,12 @@ function processPromiseRejections() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { contextFrame } = promiseInfo;
|
||||||
|
const priorContextFrame = AsyncContextFrame.exchange(contextFrame);
|
||||||
try {
|
try {
|
||||||
needPop = unhandledRejectionsMode(promise, promiseInfo, promiseAsyncId);
|
needPop = unhandledRejectionsMode(promise, promiseInfo, promiseAsyncId);
|
||||||
} finally {
|
} finally {
|
||||||
|
AsyncContextFrame.set(priorContextFrame);
|
||||||
needPop &&
|
needPop &&
|
||||||
promiseAsyncId !== undefined &&
|
promiseAsyncId !== undefined &&
|
||||||
popAsyncContext(promiseAsyncId);
|
popAsyncContext(promiseAsyncId);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
const {
|
const {
|
||||||
Array,
|
Array,
|
||||||
FunctionPrototypeBind,
|
FunctionPrototypeBind,
|
||||||
|
Symbol,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -41,6 +42,10 @@ const {
|
|||||||
|
|
||||||
const { AsyncResource } = require('async_hooks');
|
const { AsyncResource } = require('async_hooks');
|
||||||
|
|
||||||
|
const AsyncContextFrame = require('internal/async_context_frame');
|
||||||
|
|
||||||
|
const async_context_frame = Symbol('asyncContextFrame');
|
||||||
|
|
||||||
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
// *Must* match Environment::TickInfo::Fields in src/env.h.
|
||||||
const kHasTickScheduled = 0;
|
const kHasTickScheduled = 0;
|
||||||
|
|
||||||
@ -68,6 +73,9 @@ function processTicksAndRejections() {
|
|||||||
let tock;
|
let tock;
|
||||||
do {
|
do {
|
||||||
while ((tock = queue.shift()) !== null) {
|
while ((tock = queue.shift()) !== null) {
|
||||||
|
const priorContextFrame =
|
||||||
|
AsyncContextFrame.exchange(tock[async_context_frame]);
|
||||||
|
|
||||||
const asyncId = tock[async_id_symbol];
|
const asyncId = tock[async_id_symbol];
|
||||||
emitBefore(asyncId, tock[trigger_async_id_symbol], tock);
|
emitBefore(asyncId, tock[trigger_async_id_symbol], tock);
|
||||||
|
|
||||||
@ -91,6 +99,8 @@ function processTicksAndRejections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitAfter(asyncId);
|
emitAfter(asyncId);
|
||||||
|
|
||||||
|
AsyncContextFrame.set(priorContextFrame);
|
||||||
}
|
}
|
||||||
runMicrotasks();
|
runMicrotasks();
|
||||||
} while (!queue.isEmpty() || processPromiseRejections());
|
} while (!queue.isEmpty() || processPromiseRejections());
|
||||||
@ -125,6 +135,7 @@ function nextTick(callback) {
|
|||||||
const tickObject = {
|
const tickObject = {
|
||||||
[async_id_symbol]: asyncId,
|
[async_id_symbol]: asyncId,
|
||||||
[trigger_async_id_symbol]: triggerAsyncId,
|
[trigger_async_id_symbol]: triggerAsyncId,
|
||||||
|
[async_context_frame]: AsyncContextFrame.current(),
|
||||||
callback,
|
callback,
|
||||||
args,
|
args,
|
||||||
};
|
};
|
||||||
|
@ -122,6 +122,10 @@ let debug = require('internal/util/debuglog').debuglog('timer', (fn) => {
|
|||||||
debug = fn;
|
debug = fn;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const AsyncContextFrame = require('internal/async_context_frame');
|
||||||
|
|
||||||
|
const async_context_frame = Symbol('asyncContextFrame');
|
||||||
|
|
||||||
// *Must* match Environment::ImmediateInfo::Fields in src/env.h.
|
// *Must* match Environment::ImmediateInfo::Fields in src/env.h.
|
||||||
const kCount = 0;
|
const kCount = 0;
|
||||||
const kRefCount = 1;
|
const kRefCount = 1;
|
||||||
@ -162,6 +166,7 @@ function initAsyncResource(resource, type) {
|
|||||||
const asyncId = resource[async_id_symbol] = newAsyncId();
|
const asyncId = resource[async_id_symbol] = newAsyncId();
|
||||||
const triggerAsyncId =
|
const triggerAsyncId =
|
||||||
resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
|
resource[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
|
||||||
|
resource[async_context_frame] = AsyncContextFrame.current();
|
||||||
if (initHooksExist())
|
if (initHooksExist())
|
||||||
emitInit(asyncId, type, triggerAsyncId, resource);
|
emitInit(asyncId, type, triggerAsyncId, resource);
|
||||||
}
|
}
|
||||||
@ -494,6 +499,9 @@ function getTimerCallbacks(runNextTicks) {
|
|||||||
|
|
||||||
prevImmediate = immediate;
|
prevImmediate = immediate;
|
||||||
|
|
||||||
|
const priorContextFrame =
|
||||||
|
AsyncContextFrame.exchange(immediate[async_context_frame]);
|
||||||
|
|
||||||
const asyncId = immediate[async_id_symbol];
|
const asyncId = immediate[async_id_symbol];
|
||||||
emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate);
|
emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate);
|
||||||
|
|
||||||
@ -513,6 +521,8 @@ function getTimerCallbacks(runNextTicks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitAfter(asyncId);
|
emitAfter(asyncId);
|
||||||
|
|
||||||
|
AsyncContextFrame.set(priorContextFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queue === outstandingQueue)
|
if (queue === outstandingQueue)
|
||||||
@ -587,6 +597,9 @@ function getTimerCallbacks(runNextTicks) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const priorContextFrame =
|
||||||
|
AsyncContextFrame.exchange(timer[async_context_frame]);
|
||||||
|
|
||||||
emitBefore(asyncId, timer[trigger_async_id_symbol], timer);
|
emitBefore(asyncId, timer[trigger_async_id_symbol], timer);
|
||||||
|
|
||||||
let start;
|
let start;
|
||||||
@ -620,6 +633,8 @@ function getTimerCallbacks(runNextTicks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitAfter(asyncId);
|
emitAfter(asyncId);
|
||||||
|
|
||||||
|
AsyncContextFrame.set(priorContextFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If `L.peek(list)` returned nothing, the list was either empty or we have
|
// If `L.peek(list)` returned nothing, the list was either empty or we have
|
||||||
|
2
node.gyp
2
node.gyp
@ -68,6 +68,7 @@
|
|||||||
'src/api/exceptions.cc',
|
'src/api/exceptions.cc',
|
||||||
'src/api/hooks.cc',
|
'src/api/hooks.cc',
|
||||||
'src/api/utils.cc',
|
'src/api/utils.cc',
|
||||||
|
'src/async_context_frame.cc',
|
||||||
'src/async_wrap.cc',
|
'src/async_wrap.cc',
|
||||||
'src/base_object.cc',
|
'src/base_object.cc',
|
||||||
'src/cares_wrap.cc',
|
'src/cares_wrap.cc',
|
||||||
@ -185,6 +186,7 @@
|
|||||||
'src/aliased_buffer-inl.h',
|
'src/aliased_buffer-inl.h',
|
||||||
'src/aliased_struct.h',
|
'src/aliased_struct.h',
|
||||||
'src/aliased_struct-inl.h',
|
'src/aliased_struct-inl.h',
|
||||||
|
'src/async_context_frame.h',
|
||||||
'src/async_wrap.h',
|
'src/async_wrap.h',
|
||||||
'src/async_wrap-inl.h',
|
'src/async_wrap-inl.h',
|
||||||
'src/base_object.h',
|
'src/base_object.h',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "node.h"
|
#include "async_context_frame.h"
|
||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
|
#include "node.h"
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
@ -18,36 +19,55 @@ AsyncResource::AsyncResource(Isolate* isolate,
|
|||||||
: env_(Environment::GetCurrent(isolate)),
|
: env_(Environment::GetCurrent(isolate)),
|
||||||
resource_(isolate, resource) {
|
resource_(isolate, resource) {
|
||||||
CHECK_NOT_NULL(env_);
|
CHECK_NOT_NULL(env_);
|
||||||
async_context_ = EmitAsyncInit(isolate, resource, name,
|
env_->SetAsyncResourceContextFrame(
|
||||||
trigger_async_id);
|
reinterpret_cast<std::uintptr_t>(this),
|
||||||
|
{isolate, async_context_frame::current(isolate)});
|
||||||
|
async_context_ = EmitAsyncInit(isolate, resource, name, trigger_async_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncResource::~AsyncResource() {
|
AsyncResource::~AsyncResource() {
|
||||||
|
CHECK_NOT_NULL(env_);
|
||||||
|
env_->RemoveAsyncResourceContextFrame(reinterpret_cast<std::uintptr_t>(this));
|
||||||
EmitAsyncDestroy(env_, async_context_);
|
EmitAsyncDestroy(env_, async_context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeLocal<Value> AsyncResource::MakeCallback(Local<Function> callback,
|
MaybeLocal<Value> AsyncResource::MakeCallback(Local<Function> callback,
|
||||||
int argc,
|
int argc,
|
||||||
Local<Value>* argv) {
|
Local<Value>* argv) {
|
||||||
return node::MakeCallback(env_->isolate(), get_resource(),
|
auto isolate = env_->isolate();
|
||||||
callback, argc, argv,
|
auto context_frame =
|
||||||
async_context_);
|
env_->GetAsyncResourceContextFrame(reinterpret_cast<std::uintptr_t>(this))
|
||||||
|
.Get(isolate);
|
||||||
|
async_context_frame::Scope async_context_frame_scope(isolate, context_frame);
|
||||||
|
|
||||||
|
return node::MakeCallback(
|
||||||
|
isolate, get_resource(), callback, argc, argv, async_context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeLocal<Value> AsyncResource::MakeCallback(const char* method,
|
MaybeLocal<Value> AsyncResource::MakeCallback(const char* method,
|
||||||
int argc,
|
int argc,
|
||||||
Local<Value>* argv) {
|
Local<Value>* argv) {
|
||||||
return node::MakeCallback(env_->isolate(), get_resource(),
|
auto isolate = env_->isolate();
|
||||||
method, argc, argv,
|
auto context_frame =
|
||||||
async_context_);
|
env_->GetAsyncResourceContextFrame(reinterpret_cast<std::uintptr_t>(this))
|
||||||
|
.Get(isolate);
|
||||||
|
async_context_frame::Scope async_context_frame_scope(isolate, context_frame);
|
||||||
|
|
||||||
|
return node::MakeCallback(
|
||||||
|
isolate, get_resource(), method, argc, argv, async_context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeLocal<Value> AsyncResource::MakeCallback(Local<String> symbol,
|
MaybeLocal<Value> AsyncResource::MakeCallback(Local<String> symbol,
|
||||||
int argc,
|
int argc,
|
||||||
Local<Value>* argv) {
|
Local<Value>* argv) {
|
||||||
return node::MakeCallback(env_->isolate(), get_resource(),
|
auto isolate = env_->isolate();
|
||||||
symbol, argc, argv,
|
auto context_frame =
|
||||||
async_context_);
|
env_->GetAsyncResourceContextFrame(reinterpret_cast<std::uintptr_t>(this))
|
||||||
|
.Get(isolate);
|
||||||
|
async_context_frame::Scope async_context_frame_scope(isolate, context_frame);
|
||||||
|
|
||||||
|
return node::MakeCallback(
|
||||||
|
isolate, get_resource(), symbol, argc, argv, async_context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Local<Object> AsyncResource::get_resource() {
|
Local<Object> AsyncResource::get_resource() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "node.h"
|
#include "async_context_frame.h"
|
||||||
#include "async_wrap-inl.h"
|
#include "async_wrap-inl.h"
|
||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
|
#include "node.h"
|
||||||
#include "v8.h"
|
#include "v8.h"
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
@ -14,6 +15,7 @@ using v8::Local;
|
|||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
|
using v8::Undefined;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
CallbackScope::CallbackScope(Isolate* isolate,
|
CallbackScope::CallbackScope(Isolate* isolate,
|
||||||
@ -38,16 +40,18 @@ CallbackScope::~CallbackScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
|
InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
|
||||||
: InternalCallbackScope(async_wrap->env(),
|
: InternalCallbackScope(
|
||||||
|
async_wrap->env(),
|
||||||
async_wrap->object(),
|
async_wrap->object(),
|
||||||
{ async_wrap->get_async_id(),
|
{async_wrap->get_async_id(), async_wrap->get_trigger_async_id()},
|
||||||
async_wrap->get_trigger_async_id() },
|
flags,
|
||||||
flags) {}
|
async_wrap->context_frame()) {}
|
||||||
|
|
||||||
InternalCallbackScope::InternalCallbackScope(Environment* env,
|
InternalCallbackScope::InternalCallbackScope(Environment* env,
|
||||||
Local<Object> object,
|
Local<Object> object,
|
||||||
const async_context& asyncContext,
|
const async_context& asyncContext,
|
||||||
int flags)
|
int flags,
|
||||||
|
v8::Local<v8::Value> context_frame)
|
||||||
: env_(env),
|
: env_(env),
|
||||||
async_context_(asyncContext),
|
async_context_(asyncContext),
|
||||||
object_(object),
|
object_(object),
|
||||||
@ -76,6 +80,9 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
|
|||||||
|
|
||||||
isolate->SetIdle(false);
|
isolate->SetIdle(false);
|
||||||
|
|
||||||
|
prior_context_frame_.Reset(
|
||||||
|
isolate, async_context_frame::exchange(isolate, context_frame));
|
||||||
|
|
||||||
env->async_hooks()->push_async_context(
|
env->async_hooks()->push_async_context(
|
||||||
async_context_.async_id, async_context_.trigger_async_id, object);
|
async_context_.async_id, async_context_.trigger_async_id, object);
|
||||||
|
|
||||||
@ -117,9 +124,12 @@ void InternalCallbackScope::Close() {
|
|||||||
AsyncWrap::EmitAfter(env_, async_context_.async_id);
|
AsyncWrap::EmitAfter(env_, async_context_.async_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pushed_ids_)
|
if (pushed_ids_) {
|
||||||
env_->async_hooks()->pop_async_context(async_context_.async_id);
|
env_->async_hooks()->pop_async_context(async_context_.async_id);
|
||||||
|
|
||||||
|
async_context_frame::exchange(isolate, prior_context_frame_.Get(isolate));
|
||||||
|
}
|
||||||
|
|
||||||
if (failed_) return;
|
if (failed_) return;
|
||||||
|
|
||||||
if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) {
|
if (env_->async_callback_scope_depth() > 1 || skip_task_queues_) {
|
||||||
@ -173,7 +183,8 @@ MaybeLocal<Value> InternalMakeCallback(Environment* env,
|
|||||||
const Local<Function> callback,
|
const Local<Function> callback,
|
||||||
int argc,
|
int argc,
|
||||||
Local<Value> argv[],
|
Local<Value> argv[],
|
||||||
async_context asyncContext) {
|
async_context asyncContext,
|
||||||
|
Local<Value> context_frame) {
|
||||||
CHECK(!recv.IsEmpty());
|
CHECK(!recv.IsEmpty());
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
for (int i = 0; i < argc; i++)
|
for (int i = 0; i < argc; i++)
|
||||||
@ -194,7 +205,8 @@ MaybeLocal<Value> InternalMakeCallback(Environment* env,
|
|||||||
async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0;
|
async_hooks->fields()[AsyncHooks::kUsesExecutionAsyncResource] > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
InternalCallbackScope scope(env, resource, asyncContext, flags);
|
InternalCallbackScope scope(
|
||||||
|
env, resource, asyncContext, flags, context_frame);
|
||||||
if (scope.Failed()) {
|
if (scope.Failed()) {
|
||||||
return MaybeLocal<Value>();
|
return MaybeLocal<Value>();
|
||||||
}
|
}
|
||||||
@ -270,6 +282,17 @@ MaybeLocal<Value> MakeCallback(Isolate* isolate,
|
|||||||
int argc,
|
int argc,
|
||||||
Local<Value> argv[],
|
Local<Value> argv[],
|
||||||
async_context asyncContext) {
|
async_context asyncContext) {
|
||||||
|
return InternalMakeCallback(
|
||||||
|
isolate, recv, callback, argc, argv, asyncContext, Undefined(isolate));
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeLocal<Value> InternalMakeCallback(Isolate* isolate,
|
||||||
|
Local<Object> recv,
|
||||||
|
Local<Function> callback,
|
||||||
|
int argc,
|
||||||
|
Local<Value> argv[],
|
||||||
|
async_context asyncContext,
|
||||||
|
Local<Value> context_frame) {
|
||||||
// Observe the following two subtleties:
|
// Observe the following two subtleties:
|
||||||
//
|
//
|
||||||
// 1. The environment is retrieved from the callback function's context.
|
// 1. The environment is retrieved from the callback function's context.
|
||||||
@ -281,8 +304,8 @@ MaybeLocal<Value> MakeCallback(Isolate* isolate,
|
|||||||
Environment::GetCurrent(callback->GetCreationContextChecked());
|
Environment::GetCurrent(callback->GetCreationContextChecked());
|
||||||
CHECK_NOT_NULL(env);
|
CHECK_NOT_NULL(env);
|
||||||
Context::Scope context_scope(env->context());
|
Context::Scope context_scope(env->context());
|
||||||
MaybeLocal<Value> ret =
|
MaybeLocal<Value> ret = InternalMakeCallback(
|
||||||
InternalMakeCallback(env, recv, recv, callback, argc, argv, asyncContext);
|
env, recv, recv, callback, argc, argv, asyncContext, context_frame);
|
||||||
if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
|
if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
|
||||||
// This is only for legacy compatibility and we may want to look into
|
// This is only for legacy compatibility and we may want to look into
|
||||||
// removing/adjusting it.
|
// removing/adjusting it.
|
||||||
@ -315,9 +338,14 @@ MaybeLocal<Value> MakeSyncCallback(Isolate* isolate,
|
|||||||
|
|
||||||
// This is a toplevel invocation and the caller (intentionally)
|
// This is a toplevel invocation and the caller (intentionally)
|
||||||
// didn't provide any async_context to run in. Install a default context.
|
// didn't provide any async_context to run in. Install a default context.
|
||||||
MaybeLocal<Value> ret =
|
MaybeLocal<Value> ret = InternalMakeCallback(env,
|
||||||
InternalMakeCallback(env, env->process_object(), recv, callback, argc, argv,
|
env->process_object(),
|
||||||
async_context{0, 0});
|
recv,
|
||||||
|
callback,
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
async_context{0, 0},
|
||||||
|
v8::Undefined(isolate));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
89
src/async_context_frame.cc
Normal file
89
src/async_context_frame.cc
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#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::Context;
|
||||||
|
using v8::Isolate;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
namespace async_context_frame {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Scope helper
|
||||||
|
//
|
||||||
|
Scope::Scope(Isolate* isolate, Local<Value> object) : isolate_(isolate) {
|
||||||
|
auto prior = exchange(isolate, object);
|
||||||
|
prior_.Reset(isolate, prior);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope::~Scope() {
|
||||||
|
auto value = prior_.Get(isolate_);
|
||||||
|
set(isolate_, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Value> current(Isolate* isolate) {
|
||||||
|
return isolate->GetContinuationPreservedEmbedderData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(Isolate* isolate, Local<Value> value) {
|
||||||
|
auto env = Environment::GetCurrent(isolate);
|
||||||
|
if (!env->options()->async_context_frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isolate->SetContinuationPreservedEmbedderData(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: It's generally recommended to use async_context_frame::Scope
|
||||||
|
// but sometimes (such as enterWith) a direct exchange is needed.
|
||||||
|
Local<Value> exchange(Isolate* isolate, Local<Value> value) {
|
||||||
|
auto prior = current(isolate);
|
||||||
|
set(isolate, value);
|
||||||
|
return prior;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreatePerContextProperties(Local<Object> target,
|
||||||
|
Local<Value> unused,
|
||||||
|
Local<Context> context,
|
||||||
|
void* priv) {
|
||||||
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
|
||||||
|
Local<String> getContinuationPreservedEmbedderData = FIXED_ONE_BYTE_STRING(
|
||||||
|
env->isolate(), "getContinuationPreservedEmbedderData");
|
||||||
|
Local<String> setContinuationPreservedEmbedderData = FIXED_ONE_BYTE_STRING(
|
||||||
|
env->isolate(), "setContinuationPreservedEmbedderData");
|
||||||
|
|
||||||
|
// Grab the intrinsics from the binding object and expose those to our
|
||||||
|
// binding layer.
|
||||||
|
Local<Object> binding = context->GetExtrasBindingObject();
|
||||||
|
target
|
||||||
|
->Set(context,
|
||||||
|
getContinuationPreservedEmbedderData,
|
||||||
|
binding->Get(context, getContinuationPreservedEmbedderData)
|
||||||
|
.ToLocalChecked())
|
||||||
|
.Check();
|
||||||
|
target
|
||||||
|
->Set(context,
|
||||||
|
setContinuationPreservedEmbedderData,
|
||||||
|
binding->Get(context, setContinuationPreservedEmbedderData)
|
||||||
|
.ToLocalChecked())
|
||||||
|
.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace async_context_frame
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(
|
||||||
|
async_context_frame, node::async_context_frame::CreatePerContextProperties)
|
33
src/async_context_frame.h
Normal file
33
src/async_context_frame.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef SRC_ASYNC_CONTEXT_FRAME_H_
|
||||||
|
#define SRC_ASYNC_CONTEXT_FRAME_H_
|
||||||
|
|
||||||
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include "base_object.h"
|
||||||
|
#include "v8.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
namespace async_context_frame {
|
||||||
|
|
||||||
|
class Scope {
|
||||||
|
public:
|
||||||
|
explicit Scope(v8::Isolate* isolate, v8::Local<v8::Value> object);
|
||||||
|
~Scope();
|
||||||
|
|
||||||
|
private:
|
||||||
|
v8::Isolate* isolate_;
|
||||||
|
v8::Global<v8::Value> prior_;
|
||||||
|
};
|
||||||
|
|
||||||
|
v8::Local<v8::Value> current(v8::Isolate* isolate);
|
||||||
|
void set(v8::Isolate* isolate, v8::Local<v8::Value> value);
|
||||||
|
v8::Local<v8::Value> exchange(v8::Isolate* isolate, v8::Local<v8::Value> value);
|
||||||
|
|
||||||
|
} // namespace async_context_frame
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#endif // SRC_ASYNC_CONTEXT_FRAME_H_
|
@ -49,6 +49,9 @@ inline double AsyncWrap::get_trigger_async_id() const {
|
|||||||
return trigger_async_id_;
|
return trigger_async_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline v8::Local<v8::Value> AsyncWrap::context_frame() const {
|
||||||
|
return context_frame_.Get(env()->isolate());
|
||||||
|
}
|
||||||
|
|
||||||
inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
|
inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
|
||||||
const v8::Local<v8::String> symbol,
|
const v8::Local<v8::String> symbol,
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#include "async_wrap.h" // NOLINT(build/include_inline)
|
#include "async_wrap.h" // NOLINT(build/include_inline)
|
||||||
|
#include "async_context_frame.h"
|
||||||
#include "async_wrap-inl.h"
|
#include "async_wrap-inl.h"
|
||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
#include "node_errors.h"
|
#include "node_errors.h"
|
||||||
@ -513,8 +514,9 @@ AsyncWrap::AsyncWrap(Environment* env,
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncWrap::AsyncWrap(Environment* env, Local<Object> object)
|
AsyncWrap::AsyncWrap(Environment* env, Local<Object> object)
|
||||||
: BaseObject(env, object) {
|
: BaseObject(env, object),
|
||||||
}
|
context_frame_(env->isolate(),
|
||||||
|
async_context_frame::current(env->isolate())) {}
|
||||||
|
|
||||||
// This method is necessary to work around one specific problem:
|
// This method is necessary to work around one specific problem:
|
||||||
// Before the init() hook runs, if there is one, the BaseObject() constructor
|
// Before the init() hook runs, if there is one, the BaseObject() constructor
|
||||||
@ -606,8 +608,9 @@ void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id,
|
|||||||
: execution_async_id;
|
: execution_async_id;
|
||||||
trigger_async_id_ = env()->get_default_trigger_async_id();
|
trigger_async_id_ = env()->get_default_trigger_async_id();
|
||||||
|
|
||||||
|
Isolate* isolate = env()->isolate();
|
||||||
{
|
{
|
||||||
HandleScope handle_scope(env()->isolate());
|
HandleScope handle_scope(isolate);
|
||||||
Local<Object> obj = object();
|
Local<Object> obj = object();
|
||||||
CHECK(!obj.IsEmpty());
|
CHECK(!obj.IsEmpty());
|
||||||
if (resource != obj) {
|
if (resource != obj) {
|
||||||
@ -637,6 +640,8 @@ void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id,
|
|||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context_frame_.Reset(isolate, async_context_frame::current(isolate));
|
||||||
|
|
||||||
if (silent) return;
|
if (silent) return;
|
||||||
|
|
||||||
EmitAsyncInit(env(), resource,
|
EmitAsyncInit(env(), resource,
|
||||||
@ -680,8 +685,15 @@ MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
|
|||||||
|
|
||||||
ProviderType provider = provider_type();
|
ProviderType provider = provider_type();
|
||||||
async_context context { get_async_id(), get_trigger_async_id() };
|
async_context context { get_async_id(), get_trigger_async_id() };
|
||||||
MaybeLocal<Value> ret = InternalMakeCallback(
|
MaybeLocal<Value> ret =
|
||||||
env(), object(), object(), cb, argc, argv, context);
|
InternalMakeCallback(env(),
|
||||||
|
object(),
|
||||||
|
object(),
|
||||||
|
cb,
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
context,
|
||||||
|
context_frame_.Get(env()->isolate()));
|
||||||
|
|
||||||
// This is a static call with cached values because the `this` object may
|
// This is a static call with cached values because the `this` object may
|
||||||
// no longer be alive at this point.
|
// no longer be alive at this point.
|
||||||
|
@ -187,6 +187,8 @@ class AsyncWrap : public BaseObject {
|
|||||||
inline double get_async_id() const;
|
inline double get_async_id() const;
|
||||||
inline double get_trigger_async_id() const;
|
inline double get_trigger_async_id() const;
|
||||||
|
|
||||||
|
inline v8::Local<v8::Value> context_frame() const;
|
||||||
|
|
||||||
void AsyncReset(v8::Local<v8::Object> resource,
|
void AsyncReset(v8::Local<v8::Object> resource,
|
||||||
double execution_async_id = kInvalidAsyncId,
|
double execution_async_id = kInvalidAsyncId,
|
||||||
bool silent = false);
|
bool silent = false);
|
||||||
@ -239,6 +241,8 @@ class AsyncWrap : public BaseObject {
|
|||||||
// Because the values may be Reset(), cannot be made const.
|
// Because the values may be Reset(), cannot be made const.
|
||||||
double async_id_ = kInvalidAsyncId;
|
double async_id_ = kInvalidAsyncId;
|
||||||
double trigger_async_id_ = kInvalidAsyncId;
|
double trigger_async_id_ = kInvalidAsyncId;
|
||||||
|
|
||||||
|
v8::Global<v8::Value> context_frame_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
@ -934,6 +934,26 @@ inline void Environment::RemoveHeapSnapshotNearHeapLimitCallback(
|
|||||||
heap_limit);
|
heap_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Environment::SetAsyncResourceContextFrame(
|
||||||
|
std::uintptr_t async_resource_handle,
|
||||||
|
v8::Global<v8::Value>&& context_frame) {
|
||||||
|
async_resource_context_frames_.emplace(
|
||||||
|
std::make_pair(async_resource_handle, std::move(context_frame)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const v8::Global<v8::Value>& Environment::GetAsyncResourceContextFrame(
|
||||||
|
std::uintptr_t async_resource_handle) {
|
||||||
|
auto&& async_resource_context_frame =
|
||||||
|
async_resource_context_frames_.find(async_resource_handle);
|
||||||
|
CHECK_NE(async_resource_context_frame, async_resource_context_frames_.end());
|
||||||
|
|
||||||
|
return async_resource_context_frame->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Environment::RemoveAsyncResourceContextFrame(
|
||||||
|
std::uintptr_t async_resource_handle) {
|
||||||
|
async_resource_context_frames_.erase(async_resource_handle);
|
||||||
|
}
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
// These two files depend on each other. Including base_object-inl.h after this
|
// These two files depend on each other. Including base_object-inl.h after this
|
||||||
|
13
src/env.h
13
src/env.h
@ -596,7 +596,7 @@ v8::Maybe<ExitCode> EmitProcessExitInternal(Environment* env);
|
|||||||
* environment. Each environment has a principal realm. An environment can
|
* environment. Each environment has a principal realm. An environment can
|
||||||
* create multiple subsidiary synthetic realms.
|
* create multiple subsidiary synthetic realms.
|
||||||
*/
|
*/
|
||||||
class Environment : public MemoryRetainer {
|
class Environment final : public MemoryRetainer {
|
||||||
public:
|
public:
|
||||||
Environment(const Environment&) = delete;
|
Environment(const Environment&) = delete;
|
||||||
Environment& operator=(const Environment&) = delete;
|
Environment& operator=(const Environment&) = delete;
|
||||||
@ -1059,6 +1059,14 @@ class Environment : public MemoryRetainer {
|
|||||||
|
|
||||||
v8::Global<v8::Module> temporary_required_module_facade_original;
|
v8::Global<v8::Module> temporary_required_module_facade_original;
|
||||||
|
|
||||||
|
void SetAsyncResourceContextFrame(std::uintptr_t async_resource_handle,
|
||||||
|
v8::Global<v8::Value>&&);
|
||||||
|
|
||||||
|
const v8::Global<v8::Value>& GetAsyncResourceContextFrame(
|
||||||
|
std::uintptr_t async_resource_handle);
|
||||||
|
|
||||||
|
void RemoveAsyncResourceContextFrame(std::uintptr_t async_resource_handle);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>,
|
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>,
|
||||||
v8::Local<v8::Value>),
|
v8::Local<v8::Value>),
|
||||||
@ -1231,6 +1239,9 @@ class Environment : public MemoryRetainer {
|
|||||||
// track of the BackingStore for a given pointer.
|
// track of the BackingStore for a given pointer.
|
||||||
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
|
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
|
||||||
released_allocated_buffers_;
|
released_allocated_buffers_;
|
||||||
|
|
||||||
|
std::unordered_map<std::uintptr_t, v8::Global<v8::Value>>
|
||||||
|
async_resource_context_frames_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "async_context_frame.h"
|
||||||
#include "async_wrap-inl.h"
|
#include "async_wrap-inl.h"
|
||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
#define NAPI_EXPERIMENTAL
|
#define NAPI_EXPERIMENTAL
|
||||||
@ -545,7 +546,9 @@ class AsyncContext {
|
|||||||
: env_(env) {
|
: env_(env) {
|
||||||
async_id_ = node_env()->new_async_id();
|
async_id_ = node_env()->new_async_id();
|
||||||
trigger_async_id_ = node_env()->get_default_trigger_async_id();
|
trigger_async_id_ = node_env()->get_default_trigger_async_id();
|
||||||
resource_.Reset(node_env()->isolate(), resource_object);
|
v8::Isolate* isolate = node_env()->isolate();
|
||||||
|
resource_.Reset(isolate, resource_object);
|
||||||
|
context_frame_.Reset(isolate, node::async_context_frame::current(isolate));
|
||||||
lost_reference_ = false;
|
lost_reference_ = false;
|
||||||
if (externally_managed_resource) {
|
if (externally_managed_resource) {
|
||||||
resource_.SetWeak(
|
resource_.SetWeak(
|
||||||
@ -571,13 +574,15 @@ class AsyncContext {
|
|||||||
int argc,
|
int argc,
|
||||||
v8::Local<v8::Value> argv[]) {
|
v8::Local<v8::Value> argv[]) {
|
||||||
EnsureReference();
|
EnsureReference();
|
||||||
return node::InternalMakeCallback(node_env(),
|
return node::InternalMakeCallback(
|
||||||
|
node_env(),
|
||||||
resource(),
|
resource(),
|
||||||
recv,
|
recv,
|
||||||
callback,
|
callback,
|
||||||
argc,
|
argc,
|
||||||
argv,
|
argv,
|
||||||
{async_id_, trigger_async_id_});
|
{async_id_, trigger_async_id_},
|
||||||
|
context_frame_.Get(node_env()->isolate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline napi_callback_scope OpenCallbackScope() {
|
inline napi_callback_scope OpenCallbackScope() {
|
||||||
@ -633,6 +638,7 @@ class AsyncContext {
|
|||||||
double trigger_async_id_;
|
double trigger_async_id_;
|
||||||
v8::Global<v8::Object> resource_;
|
v8::Global<v8::Object> resource_;
|
||||||
bool lost_reference_;
|
bool lost_reference_;
|
||||||
|
v8::Global<v8::Value> context_frame_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
// The binding IDs that start with 'internal_only' are not exposed to the user
|
// The binding IDs that start with 'internal_only' are not exposed to the user
|
||||||
// land even from internal/test/binding module under --expose-internals.
|
// land even from internal/test/binding module under --expose-internals.
|
||||||
#define NODE_BUILTIN_STANDARD_BINDINGS(V) \
|
#define NODE_BUILTIN_STANDARD_BINDINGS(V) \
|
||||||
|
V(async_context_frame) \
|
||||||
V(async_wrap) \
|
V(async_wrap) \
|
||||||
V(blob) \
|
V(blob) \
|
||||||
V(block_list) \
|
V(block_list) \
|
||||||
|
@ -212,7 +212,17 @@ v8::MaybeLocal<v8::Value> InternalMakeCallback(
|
|||||||
const v8::Local<v8::Function> callback,
|
const v8::Local<v8::Function> callback,
|
||||||
int argc,
|
int argc,
|
||||||
v8::Local<v8::Value> argv[],
|
v8::Local<v8::Value> argv[],
|
||||||
async_context asyncContext);
|
async_context asyncContext,
|
||||||
|
v8::Local<v8::Value> context_frame);
|
||||||
|
|
||||||
|
v8::MaybeLocal<v8::Value> InternalMakeCallback(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::Object> recv,
|
||||||
|
const v8::Local<v8::Function> callback,
|
||||||
|
int argc,
|
||||||
|
v8::Local<v8::Value> argv[],
|
||||||
|
async_context asyncContext,
|
||||||
|
v8::Local<v8::Value> context_frame);
|
||||||
|
|
||||||
v8::MaybeLocal<v8::Value> MakeSyncCallback(v8::Isolate* isolate,
|
v8::MaybeLocal<v8::Value> MakeSyncCallback(v8::Isolate* isolate,
|
||||||
v8::Local<v8::Object> recv,
|
v8::Local<v8::Object> recv,
|
||||||
@ -232,10 +242,13 @@ class InternalCallbackScope {
|
|||||||
// compatibility issues, but it shouldn't.)
|
// compatibility issues, but it shouldn't.)
|
||||||
kSkipTaskQueues = 2
|
kSkipTaskQueues = 2
|
||||||
};
|
};
|
||||||
InternalCallbackScope(Environment* env,
|
InternalCallbackScope(
|
||||||
|
Environment* env,
|
||||||
v8::Local<v8::Object> object,
|
v8::Local<v8::Object> object,
|
||||||
const async_context& asyncContext,
|
const async_context& asyncContext,
|
||||||
int flags = kNoFlags);
|
int flags = kNoFlags,
|
||||||
|
v8::Local<v8::Value> context_frame = v8::Local<v8::Value>());
|
||||||
|
|
||||||
// Utility that can be used by AsyncWrap classes.
|
// Utility that can be used by AsyncWrap classes.
|
||||||
explicit InternalCallbackScope(AsyncWrap* async_wrap, int flags = 0);
|
explicit InternalCallbackScope(AsyncWrap* async_wrap, int flags = 0);
|
||||||
~InternalCallbackScope();
|
~InternalCallbackScope();
|
||||||
@ -253,6 +266,7 @@ class InternalCallbackScope {
|
|||||||
bool failed_ = false;
|
bool failed_ = false;
|
||||||
bool pushed_ids_ = false;
|
bool pushed_ids_ = false;
|
||||||
bool closed_ = false;
|
bool closed_ = false;
|
||||||
|
v8::Global<v8::Value> prior_context_frame_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebugSealHandleScope {
|
class DebugSealHandleScope {
|
||||||
|
@ -487,6 +487,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||||||
AddOption(
|
AddOption(
|
||||||
"--experimental-wasi-unstable-preview1", "", NoOp{}, kAllowedInEnvvar);
|
"--experimental-wasi-unstable-preview1", "", NoOp{}, kAllowedInEnvvar);
|
||||||
AddOption("--expose-gc", "expose gc extension", V8Option{}, kAllowedInEnvvar);
|
AddOption("--expose-gc", "expose gc extension", V8Option{}, kAllowedInEnvvar);
|
||||||
|
AddOption("--experimental-async-context-frame",
|
||||||
|
"Improve AsyncLocalStorage performance with AsyncContextFrame",
|
||||||
|
&EnvironmentOptions::async_context_frame,
|
||||||
|
kAllowedInEnvvar);
|
||||||
AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals);
|
AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals);
|
||||||
AddOption("--frozen-intrinsics",
|
AddOption("--frozen-intrinsics",
|
||||||
"experimental frozen intrinsics support",
|
"experimental frozen intrinsics support",
|
||||||
|
@ -136,6 +136,7 @@ class EnvironmentOptions : public Options {
|
|||||||
bool allow_worker_threads = false;
|
bool allow_worker_threads = false;
|
||||||
bool experimental_repl_await = true;
|
bool experimental_repl_await = true;
|
||||||
bool experimental_vm_modules = false;
|
bool experimental_vm_modules = false;
|
||||||
|
bool async_context_frame = false;
|
||||||
bool expose_internals = false;
|
bool expose_internals = false;
|
||||||
bool force_node_api_uncaught_exceptions_policy = false;
|
bool force_node_api_uncaught_exceptions_policy = false;
|
||||||
bool frozen_intrinsics = false;
|
bool frozen_intrinsics = false;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Error: test
|
Error: test
|
||||||
at one (*fixtures*async-error.js:4:9)
|
at one (*fixtures*async-error.js:4:9)
|
||||||
at two (*fixtures*async-error.js:17:9)
|
at two (*fixtures*async-error.js:17:9)
|
||||||
at process.processTicksAndRejections (node:internal*process*task_queues:95:5)
|
at process.processTicksAndRejections (node:internal*process*task_queues:105:5)
|
||||||
at async three (*fixtures*async-error.js:20:3)
|
at async three (*fixtures*async-error.js:20:3)
|
||||||
at async four (*fixtures*async-error.js:24:3)
|
at async four (*fixtures*async-error.js:24:3)
|
||||||
at async main (*async_error_nexttick_main.js:7:5)
|
at async main (*async_error_nexttick_main.js:7:5)
|
||||||
|
59
test/parallel/test-async-context-frame.mjs
Normal file
59
test/parallel/test-async-context-frame.mjs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { isWindows } from '../common/index.mjs';
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import { once } from 'node:events';
|
||||||
|
import { opendir } from 'node:fs/promises';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { describe, it } from 'node:test';
|
||||||
|
import { sep } from 'node:path';
|
||||||
|
|
||||||
|
const python = process.env.PYTHON || (isWindows ? 'python' : 'python3');
|
||||||
|
|
||||||
|
const testRunner = fileURLToPath(
|
||||||
|
new URL('../../tools/test.py', import.meta.url)
|
||||||
|
);
|
||||||
|
|
||||||
|
const setNames = ['async-hooks', 'parallel'];
|
||||||
|
|
||||||
|
// Get all test names for each set
|
||||||
|
const testSets = await Promise.all(setNames.map(async (name) => {
|
||||||
|
const path = fileURLToPath(new URL(`../${name}`, import.meta.url));
|
||||||
|
const dir = await opendir(path);
|
||||||
|
|
||||||
|
const tests = [];
|
||||||
|
for await (const entry of dir) {
|
||||||
|
if (entry.name.startsWith('test-async-local-storage-')) {
|
||||||
|
tests.push(entry.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
tests
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Merge test sets with set name prefix
|
||||||
|
const tests = testSets.reduce((m, v) => {
|
||||||
|
for (const test of v.tests) {
|
||||||
|
m.push(`${v.name}${sep}${test}`);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
describe('AsyncContextFrame', {
|
||||||
|
concurrency: tests.length
|
||||||
|
}, () => {
|
||||||
|
for (const test of tests) {
|
||||||
|
it(test, async () => {
|
||||||
|
const proc = spawn(python, [
|
||||||
|
testRunner,
|
||||||
|
'--node-args=--experimental-async-context-frame',
|
||||||
|
test,
|
||||||
|
], {
|
||||||
|
stdio: ['ignore', 'ignore', 'inherit'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await once(proc, 'exit');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -5,20 +5,23 @@ const { AsyncLocalStorage } = require('async_hooks');
|
|||||||
|
|
||||||
const als = new AsyncLocalStorage();
|
const als = new AsyncLocalStorage();
|
||||||
|
|
||||||
// Make sure _propagate function exists.
|
// The _propagate function only exists on the old JavaScript implementation.
|
||||||
assert.ok(typeof als._propagate === 'function');
|
if (typeof als._propagate === 'function') {
|
||||||
|
// The als instance should be getting removed from the storageList in
|
||||||
// The als instance should be getting removed from the storageList in
|
// lib/async_hooks.js when exit(...) is called, therefore when the nested runs
|
||||||
// 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
|
||||||
// are called there should be no copy of the als in the storageList to run the
|
// _propagate method on.
|
||||||
// _propagate method on.
|
als._propagate = common.mustNotCall('_propagate() should not be called');
|
||||||
als._propagate = common.mustNotCall('_propagate() should not be called');
|
}
|
||||||
|
|
||||||
const done = common.mustCall();
|
const done = common.mustCall();
|
||||||
|
|
||||||
|
const data = true;
|
||||||
|
|
||||||
function run(count) {
|
function run(count) {
|
||||||
if (count === 0) return done();
|
if (count === 0) return done();
|
||||||
als.run({}, () => {
|
assert.notStrictEqual(als.getStore(), data);
|
||||||
|
als.run(data, () => {
|
||||||
als.exit(run, --count);
|
als.exit(run, --count);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ expected.beforePreExec = new Set([
|
|||||||
'NativeModule internal/errors',
|
'NativeModule internal/errors',
|
||||||
'Internal Binding config',
|
'Internal Binding config',
|
||||||
'Internal Binding timers',
|
'Internal Binding timers',
|
||||||
|
'Internal Binding async_context_frame',
|
||||||
|
'NativeModule internal/async_context_frame',
|
||||||
'Internal Binding async_wrap',
|
'Internal Binding async_wrap',
|
||||||
'Internal Binding task_queue',
|
'Internal Binding task_queue',
|
||||||
'Internal Binding symbols',
|
'Internal Binding symbols',
|
||||||
|
Loading…
Reference in New Issue
Block a user