diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 8a01b22c47c..ead62be01d4 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -780,6 +780,24 @@ On the other hand, `node-inspect` may be installed locally through a package
manager, as it is published on the npm registry under the same name. No source
code modification is necessary if that is done.
+
+### DEP0085: AsyncHooks Sensitive API
+
+Type: Runtime
+
+The AsyncHooks Sensitive API was never documented and had various of minor
+issues, see https://github.com/nodejs/node/issues/15572. Use the `AsyncResource`
+API instead.
+
+
+
+### DEP0086: Remove runInAsyncIdScope
+
+Type: Runtime
+
+`runInAsyncIdScope` doesn't emit the `before` or `after` event and can thus
+cause a lot of issues. See https://github.com/nodejs/node/issues/14328 for more
+details.
[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
[`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array
diff --git a/lib/async_hooks.js b/lib/async_hooks.js
index 3f00a772bef..78dff7218c1 100644
--- a/lib/async_hooks.js
+++ b/lib/async_hooks.js
@@ -1,118 +1,50 @@
'use strict';
-const async_wrap = process.binding('async_wrap');
const errors = require('internal/errors');
-/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
- * Environment::AsyncHooks::fields_[]. Each index tracks the number of active
- * hooks for each type.
- *
- * async_id_fields is a Float64Array wrapping the double array of
- * Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for
- * the various asynchronous states of the application. These are:
- * kExecutionAsyncId: The async_id assigned to the resource responsible for the
- * current execution stack.
- * kTriggerAsyncId: The trigger_async_id of the resource responsible for
- * the current execution stack.
- * kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
- * kInitTriggerAsyncId: Written immediately before a resource's constructor
- * that sets the value of the init()'s triggerAsyncId. The order of
- * retrieving the triggerAsyncId value is passing directly to the
- * constructor -> value set in kInitTriggerAsyncId -> executionAsyncId of
- * the current resource.
- */
-const { async_hook_fields, async_id_fields } = async_wrap;
-// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
-// Environment::AsyncHooks::ids_stack_ tracks the resource responsible for the
-// current execution stack. This is unwound as each resource exits. In the case
-// of a fatal exception this stack is emptied after calling each hook's after()
-// callback.
+const internalUtil = require('internal/util');
+const async_wrap = process.binding('async_wrap');
+const internal_async_hooks = require('internal/async_hooks');
+
+// Get functions
+// Only used to support a deprecated API. pushAsyncIds, popAsyncIds should
+// never be directly in this manner.
const { pushAsyncIds, popAsyncIds } = async_wrap;
-// For performance reasons, only track Proimses when a hook is enabled.
-const { enablePromiseHook, disablePromiseHook } = async_wrap;
// For userland AsyncResources, make sure to emit a destroy event when the
// resource gets gced.
const { registerDestroyHook } = async_wrap;
-// Properties in active_hooks are used to keep track of the set of hooks being
-// executed in case another hook is enabled/disabled. The new set of hooks is
-// then restored once the active set of hooks is finished executing.
-const active_hooks = {
- // Array of all AsyncHooks that will be iterated whenever an async event
- // fires. Using var instead of (preferably const) in order to assign
- // active_hooks.tmp_array if a hook is enabled/disabled during hook
- // execution.
- array: [],
- // Use a counter to track nested calls of async hook callbacks and make sure
- // the active_hooks.array isn't altered mid execution.
- call_depth: 0,
- // Use to temporarily store and updated active_hooks.array if the user
- // enables or disables a hook while hooks are being processed. If a hook is
- // enabled() or disabled() during hook execution then the current set of
- // active hooks is duplicated and set equal to active_hooks.tmp_array. Any
- // subsequent changes are on the duplicated array. When all hooks have
- // completed executing active_hooks.tmp_array is assigned to
- // active_hooks.array.
- tmp_array: null,
- // Keep track of the field counts held in active_hooks.tmp_array. Because the
- // async_hook_fields can't be reassigned, store each uint32 in an array that
- // is written back to async_hook_fields when active_hooks.array is restored.
- tmp_fields: null
-};
+const {
+ // Private API
+ getHookArrays,
+ enableHooks,
+ disableHooks,
+ // Sensitive Embedder API
+ newUid,
+ initTriggerId,
+ setInitTriggerId,
+ emitInit,
+ emitBefore,
+ emitAfter,
+ emitDestroy,
+} = internal_async_hooks;
+// Get fields
+const { async_id_fields } = async_wrap;
-// Each constant tracks how many callbacks there are for any given step of
-// async execution. These are tracked so if the user didn't include callbacks
-// for a given step, that step can bail out early.
-const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
- kCheck, kExecutionAsyncId, kTriggerAsyncId, kAsyncIdCounter,
- kInitTriggerAsyncId } = async_wrap.constants;
+// Get symbols
+const {
+ init_symbol, before_symbol, after_symbol, destroy_symbol,
+ promise_resolve_symbol
+} = internal_async_hooks.symbols;
-// Symbols used to store the respective ids on both AsyncResource instances and
-// internal resources. They will also be assigned to arbitrary objects passed
-// in by the user that take place of internally constructed objects.
const { async_id_symbol, trigger_async_id_symbol } = async_wrap;
-// Used in AsyncHook and AsyncResource.
-const init_symbol = Symbol('init');
-const before_symbol = Symbol('before');
-const after_symbol = Symbol('after');
-const destroy_symbol = Symbol('destroy');
-const promise_resolve_symbol = Symbol('promiseResolve');
-const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
-const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
-const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
-const emitPromiseResolveNative =
- emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
+// Get constants
+const {
+ kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
+ kExecutionAsyncId, kTriggerAsyncId
+} = async_wrap.constants;
-// TODO(refack): move to node-config.cc
-const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
-
-// Setup the callbacks that node::AsyncWrap will call when there are hooks to
-// process. They use the same functions as the JS embedder API. These callbacks
-// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
-// and the cost of doing so is negligible.
-async_wrap.setupHooks({ init: emitInitNative,
- before: emitBeforeNative,
- after: emitAfterNative,
- destroy: emitDestroyNative,
- promise_resolve: emitPromiseResolveNative });
-
-// Used to fatally abort the process if a callback throws.
-function fatalError(e) {
- if (typeof e.stack === 'string') {
- process._rawDebug(e.stack);
- } else {
- const o = { message: e };
- Error.captureStackTrace(o, fatalError);
- process._rawDebug(o.stack);
- }
- if (process.execArgv.some((e) => abort_regex.test(e))) {
- process.abort();
- }
- process.exit(1);
-}
-
-
-// Public API //
+// Listener API //
class AsyncHook {
constructor({ init, before, after, destroy, promiseResolve }) {
@@ -160,8 +92,7 @@ class AsyncHook {
hooks_array.push(this);
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
- enablePromiseHook();
- hook_fields[kCheck] += 1;
+ enableHooks();
}
return this;
@@ -186,8 +117,7 @@ class AsyncHook {
hooks_array.splice(index, 1);
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
- disablePromiseHook();
- hook_fields[kCheck] -= 1;
+ disableHooks();
}
return this;
@@ -195,47 +125,6 @@ class AsyncHook {
}
-function getHookArrays() {
- if (active_hooks.call_depth === 0)
- return [active_hooks.array, async_hook_fields];
- // If this hook is being enabled while in the middle of processing the array
- // of currently active hooks then duplicate the current set of active hooks
- // and store this there. This shouldn't fire until the next time hooks are
- // processed.
- if (active_hooks.tmp_array === null)
- storeActiveHooks();
- return [active_hooks.tmp_array, active_hooks.tmp_fields];
-}
-
-
-function storeActiveHooks() {
- active_hooks.tmp_array = active_hooks.array.slice();
- // Don't want to make the assumption that kInit to kDestroy are indexes 0 to
- // 4. So do this the long way.
- active_hooks.tmp_fields = [];
- active_hooks.tmp_fields[kInit] = async_hook_fields[kInit];
- active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
- active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
- active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
- active_hooks.tmp_fields[kPromiseResolve] = async_hook_fields[kPromiseResolve];
-}
-
-
-// Then restore the correct hooks array in case any hooks were added/removed
-// during hook callback execution.
-function restoreActiveHooks() {
- active_hooks.array = active_hooks.tmp_array;
- async_hook_fields[kInit] = active_hooks.tmp_fields[kInit];
- async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
- async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
- async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
- async_hook_fields[kPromiseResolve] = active_hooks.tmp_fields[kPromiseResolve];
-
- active_hooks.tmp_array = null;
- active_hooks.tmp_fields = null;
-}
-
-
function createHook(fns) {
return new AsyncHook(fns);
}
@@ -250,15 +139,6 @@ function triggerAsyncId() {
return async_id_fields[kTriggerAsyncId];
}
-function validateAsyncId(asyncId, type) {
- // Skip validation when async_hooks is disabled
- if (async_hook_fields[kCheck] <= 0) return;
-
- if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
- fatalError(new errors.RangeError('ERR_INVALID_ASYNC_ID', type, asyncId));
- }
-}
-
// Embedder API //
@@ -284,12 +164,12 @@ class AsyncResource {
triggerAsyncId);
}
- this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
+ this[async_id_symbol] = newUid();
this[trigger_async_id_symbol] = triggerAsyncId;
// this prop name (destroyed) has to be synchronized with C++
this[destroyedSymbol] = { destroyed: false };
- emitInitScript(
+ emitInit(
this[async_id_symbol], type, this[trigger_async_id_symbol], this
);
@@ -299,18 +179,18 @@ class AsyncResource {
}
emitBefore() {
- emitBeforeScript(this[async_id_symbol], this[trigger_async_id_symbol]);
+ emitBefore(this[async_id_symbol], this[trigger_async_id_symbol]);
return this;
}
emitAfter() {
- emitAfterScript(this[async_id_symbol]);
+ emitAfter(this[async_id_symbol]);
return this;
}
emitDestroy() {
this[destroyedSymbol].destroyed = true;
- emitDestroyScript(this[async_id_symbol]);
+ emitDestroy(this[async_id_symbol]);
return this;
}
@@ -336,168 +216,6 @@ function runInAsyncIdScope(asyncId, cb) {
}
}
-
-// Sensitive Embedder API //
-
-// Increment the internal id counter and return the value. Important that the
-// counter increment first. Since it's done the same way in
-// Environment::new_async_uid()
-function newUid() {
- return ++async_id_fields[kAsyncIdCounter];
-}
-
-
-// Return the triggerAsyncId meant for the constructor calling it. It's up to
-// the user to safeguard this call and make sure it's zero'd out when the
-// constructor is complete.
-function initTriggerId() {
- var triggerAsyncId = async_id_fields[kInitTriggerAsyncId];
- // Reset value after it's been called so the next constructor doesn't
- // inherit it by accident.
- async_id_fields[kInitTriggerAsyncId] = 0;
- if (triggerAsyncId <= 0)
- triggerAsyncId = async_id_fields[kExecutionAsyncId];
- return triggerAsyncId;
-}
-
-
-function setInitTriggerId(triggerAsyncId) {
- // CHECK(Number.isSafeInteger(triggerAsyncId))
- // CHECK(triggerAsyncId > 0)
- async_id_fields[kInitTriggerAsyncId] = triggerAsyncId;
-}
-
-
-function emitInitScript(asyncId, type, triggerAsyncId, resource) {
- validateAsyncId(asyncId, 'asyncId');
- if (triggerAsyncId !== null)
- validateAsyncId(triggerAsyncId, 'triggerAsyncId');
- if (async_hook_fields[kCheck] > 0 &&
- (typeof type !== 'string' || type.length <= 0)) {
- throw new errors.TypeError('ERR_ASYNC_TYPE', type);
- }
-
- // Short circuit all checks for the common case. Which is that no hooks have
- // been set. Do this to remove performance impact for embedders (and core).
- if (async_hook_fields[kInit] === 0)
- return;
-
- // This can run after the early return check b/c running this function
- // manually means that the embedder must have used initTriggerId().
- if (triggerAsyncId === null) {
- triggerAsyncId = initTriggerId();
- } else {
- // If a triggerAsyncId was passed, any kInitTriggerAsyncId still must be
- // null'd.
- async_id_fields[kInitTriggerAsyncId] = 0;
- }
-
- emitInitNative(asyncId, type, triggerAsyncId, resource);
-}
-
-function emitHookFactory(symbol, name) {
- // Called from native. The asyncId stack handling is taken care of there
- // before this is called.
- // eslint-disable-next-line func-style
- const fn = function(asyncId) {
- active_hooks.call_depth += 1;
- // Use a single try/catch for all hook to avoid setting up one per
- // iteration.
- try {
- for (var i = 0; i < active_hooks.array.length; i++) {
- if (typeof active_hooks.array[i][symbol] === 'function') {
- active_hooks.array[i][symbol](asyncId);
- }
- }
- } catch (e) {
- fatalError(e);
- } finally {
- active_hooks.call_depth -= 1;
- }
-
- // Hooks can only be restored if there have been no recursive hook calls.
- // Also the active hooks do not need to be restored if enable()/disable()
- // weren't called during hook execution, in which case
- // active_hooks.tmp_array will be null.
- if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
- restoreActiveHooks();
- }
- };
-
- // Set the name property of the anonymous function as it looks good in the
- // stack trace.
- Object.defineProperty(fn, 'name', {
- value: name
- });
- return fn;
-}
-
-
-function emitBeforeScript(asyncId, triggerAsyncId) {
- // Validate the ids. An id of -1 means it was never set and is visible on the
- // call graph. An id < -1 should never happen in any circumstance. Throw
- // on user calls because async state should still be recoverable.
- validateAsyncId(asyncId, 'asyncId');
- validateAsyncId(triggerAsyncId, 'triggerAsyncId');
-
- pushAsyncIds(asyncId, triggerAsyncId);
-
- if (async_hook_fields[kBefore] > 0)
- emitBeforeNative(asyncId);
-}
-
-
-function emitAfterScript(asyncId) {
- validateAsyncId(asyncId, 'asyncId');
-
- if (async_hook_fields[kAfter] > 0)
- emitAfterNative(asyncId);
-
- popAsyncIds(asyncId);
-}
-
-
-function emitDestroyScript(asyncId) {
- validateAsyncId(asyncId, 'asyncId');
-
- // Return early if there are no destroy callbacks, or invalid asyncId.
- if (async_hook_fields[kDestroy] === 0 || asyncId <= 0)
- return;
- async_wrap.queueDestroyAsyncId(asyncId);
-}
-
-
-// Used by C++ to call all init() callbacks. Because some state can be setup
-// from C++ there's no need to perform all the same operations as in
-// emitInitScript.
-function emitInitNative(asyncId, type, triggerAsyncId, resource) {
- active_hooks.call_depth += 1;
- // Use a single try/catch for all hook to avoid setting up one per iteration.
- try {
- for (var i = 0; i < active_hooks.array.length; i++) {
- if (typeof active_hooks.array[i][init_symbol] === 'function') {
- active_hooks.array[i][init_symbol](
- asyncId, type, triggerAsyncId,
- resource
- );
- }
- }
- } catch (e) {
- fatalError(e);
- } finally {
- active_hooks.call_depth -= 1;
- }
-
- // Hooks can only be restored if there have been no recursive hook calls.
- // Also the active hooks do not need to be restored if enable()/disable()
- // weren't called during hook execution, in which case active_hooks.tmp_array
- // will be null.
- if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
- restoreActiveHooks();
- }
-}
-
-
// Placing all exports down here because the exported classes won't export
// otherwise.
module.exports = {
@@ -507,13 +225,62 @@ module.exports = {
triggerAsyncId,
// Embedder API
AsyncResource,
- runInAsyncIdScope,
- // Sensitive Embedder API
- newUid,
- initTriggerId,
- setInitTriggerId,
- emitInit: emitInitScript,
- emitBefore: emitBeforeScript,
- emitAfter: emitAfterScript,
- emitDestroy: emitDestroyScript,
};
+
+// Deprecated API //
+
+Object.defineProperty(module.exports, 'runInAsyncIdScope', {
+ get: internalUtil.deprecate(function() {
+ return runInAsyncIdScope;
+ }, 'async_hooks.runInAsyncIdScope is deprecated. ' +
+ 'Create an AsyncResource instead.', 'DEP0086')
+});
+
+Object.defineProperty(module.exports, 'newUid', {
+ get: internalUtil.deprecate(function() {
+ return newUid;
+ }, 'async_hooks.newUid is deprecated. ' +
+ 'Use AsyncResource instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'initTriggerId', {
+ get: internalUtil.deprecate(function() {
+ return initTriggerId;
+ }, 'async_hooks.initTriggerId is deprecated. ' +
+ 'Use the AsyncResource default instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'setInitTriggerId', {
+ get: internalUtil.deprecate(function() {
+ return setInitTriggerId;
+ }, 'async_hooks.setInitTriggerId is deprecated. ' +
+ 'Use the triggerAsyncId parameter in AsyncResource instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitInit', {
+ get: internalUtil.deprecate(function() {
+ return emitInit;
+ }, 'async_hooks.emitInit is deprecated. ' +
+ 'Use AsyncResource constructor instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitBefore', {
+ get: internalUtil.deprecate(function() {
+ return emitBefore;
+ }, 'async_hooks.emitBefore is deprecated. ' +
+ 'Use AsyncResource.emitBefore instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitAfter', {
+ get: internalUtil.deprecate(function() {
+ return emitAfter;
+ }, 'async_hooks.emitAfter is deprecated. ' +
+ 'Use AsyncResource.emitAfter instead.', 'DEP0085')
+});
+
+Object.defineProperty(module.exports, 'emitDestroy', {
+ get: internalUtil.deprecate(function() {
+ return emitDestroy;
+ }, 'async_hooks.emitDestroy is deprecated. ' +
+ 'Use AsyncResource.emitDestroy instead.', 'DEP0085')
+});
diff --git a/lib/dgram.js b/lib/dgram.js
index 94e3a9b38e3..ec60765aa48 100644
--- a/lib/dgram.js
+++ b/lib/dgram.js
@@ -28,7 +28,7 @@ const dns = require('dns');
const util = require('util');
const { isUint8Array } = require('internal/util/types');
const EventEmitter = require('events');
-const { setInitTriggerId } = require('async_hooks');
+const { setInitTriggerId } = require('internal/async_hooks');
const { UV_UDP_REUSEADDR } = process.binding('constants').os;
const { async_id_symbol } = process.binding('async_wrap');
const { nextTick } = require('internal/process/next_tick');
diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js
new file mode 100644
index 00000000000..5964a847fc0
--- /dev/null
+++ b/lib/internal/async_hooks.js
@@ -0,0 +1,349 @@
+'use strict';
+
+const errors = require('internal/errors');
+const async_wrap = process.binding('async_wrap');
+/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
+ * Environment::AsyncHooks::fields_[]. Each index tracks the number of active
+ * hooks for each type.
+ *
+ * async_id_fields is a Float64Array wrapping the double array of
+ * Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for
+ * the various asynchronous states of the application. These are:
+ * kExecutionAsyncId: The async_id assigned to the resource responsible for the
+ * current execution stack.
+ * kTriggerAsyncId: The trigger_async_id of the resource responsible for
+ * the current execution stack.
+ * kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
+ * kInitTriggerAsyncId: Written immediately before a resource's constructor
+ * that sets the value of the init()'s triggerAsyncId. The order of
+ * retrieving the triggerAsyncId value is passing directly to the
+ * constructor -> value set in kInitTriggerAsyncId -> executionAsyncId of
+ * the current resource.
+ */
+const { async_hook_fields, async_id_fields } = async_wrap;
+// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
+// Environment::AsyncHooks::ids_stack_ tracks the resource responsible for the
+// current execution stack. This is unwound as each resource exits. In the case
+// of a fatal exception this stack is emptied after calling each hook's after()
+// callback.
+const { pushAsyncIds, popAsyncIds } = async_wrap;
+// For performance reasons, only track Proimses when a hook is enabled.
+const { enablePromiseHook, disablePromiseHook } = async_wrap;
+// Properties in active_hooks are used to keep track of the set of hooks being
+// executed in case another hook is enabled/disabled. The new set of hooks is
+// then restored once the active set of hooks is finished executing.
+const active_hooks = {
+ // Array of all AsyncHooks that will be iterated whenever an async event
+ // fires. Using var instead of (preferably const) in order to assign
+ // active_hooks.tmp_array if a hook is enabled/disabled during hook
+ // execution.
+ array: [],
+ // Use a counter to track nested calls of async hook callbacks and make sure
+ // the active_hooks.array isn't altered mid execution.
+ call_depth: 0,
+ // Use to temporarily store and updated active_hooks.array if the user
+ // enables or disables a hook while hooks are being processed. If a hook is
+ // enabled() or disabled() during hook execution then the current set of
+ // active hooks is duplicated and set equal to active_hooks.tmp_array. Any
+ // subsequent changes are on the duplicated array. When all hooks have
+ // completed executing active_hooks.tmp_array is assigned to
+ // active_hooks.array.
+ tmp_array: null,
+ // Keep track of the field counts held in active_hooks.tmp_array. Because the
+ // async_hook_fields can't be reassigned, store each uint32 in an array that
+ // is written back to async_hook_fields when active_hooks.array is restored.
+ tmp_fields: null
+};
+
+
+// Each constant tracks how many callbacks there are for any given step of
+// async execution. These are tracked so if the user didn't include callbacks
+// for a given step, that step can bail out early.
+const { kInit, kBefore, kAfter, kDestroy, kPromiseResolve,
+ kCheck, kExecutionAsyncId, kAsyncIdCounter,
+ kInitTriggerAsyncId } = async_wrap.constants;
+
+// Used in AsyncHook and AsyncResource.
+const init_symbol = Symbol('init');
+const before_symbol = Symbol('before');
+const after_symbol = Symbol('after');
+const destroy_symbol = Symbol('destroy');
+const promise_resolve_symbol = Symbol('promiseResolve');
+const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
+const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
+const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
+const emitPromiseResolveNative =
+ emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
+
+// TODO(refack): move to node-config.cc
+const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
+
+// Setup the callbacks that node::AsyncWrap will call when there are hooks to
+// process. They use the same functions as the JS embedder API. These callbacks
+// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
+// and the cost of doing so is negligible.
+async_wrap.setupHooks({ init: emitInitNative,
+ before: emitBeforeNative,
+ after: emitAfterNative,
+ destroy: emitDestroyNative,
+ promise_resolve: emitPromiseResolveNative });
+
+// Used to fatally abort the process if a callback throws.
+function fatalError(e) {
+ if (typeof e.stack === 'string') {
+ process._rawDebug(e.stack);
+ } else {
+ const o = { message: e };
+ Error.captureStackTrace(o, fatalError);
+ process._rawDebug(o.stack);
+ }
+ if (process.execArgv.some((e) => abort_regex.test(e))) {
+ process.abort();
+ }
+ process.exit(1);
+}
+
+
+function validateAsyncId(asyncId, type) {
+ // Skip validation when async_hooks is disabled
+ if (async_hook_fields[kCheck] <= 0) return;
+
+ if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
+ fatalError(new errors.RangeError('ERR_INVALID_ASYNC_ID', type, asyncId));
+ }
+}
+
+// Emit From Native //
+
+// Used by C++ to call all init() callbacks. Because some state can be setup
+// from C++ there's no need to perform all the same operations as in
+// emitInitScript.
+function emitInitNative(asyncId, type, triggerAsyncId, resource) {
+ active_hooks.call_depth += 1;
+ // Use a single try/catch for all hook to avoid setting up one per iteration.
+ try {
+ for (var i = 0; i < active_hooks.array.length; i++) {
+ if (typeof active_hooks.array[i][init_symbol] === 'function') {
+ active_hooks.array[i][init_symbol](
+ asyncId, type, triggerAsyncId,
+ resource
+ );
+ }
+ }
+ } catch (e) {
+ fatalError(e);
+ } finally {
+ active_hooks.call_depth -= 1;
+ }
+
+ // Hooks can only be restored if there have been no recursive hook calls.
+ // Also the active hooks do not need to be restored if enable()/disable()
+ // weren't called during hook execution, in which case active_hooks.tmp_array
+ // will be null.
+ if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
+ restoreActiveHooks();
+ }
+}
+
+
+function emitHookFactory(symbol, name) {
+ // Called from native. The asyncId stack handling is taken care of there
+ // before this is called.
+ // eslint-disable-next-line func-style
+ const fn = function(asyncId) {
+ active_hooks.call_depth += 1;
+ // Use a single try/catch for all hook to avoid setting up one per
+ // iteration.
+ try {
+ for (var i = 0; i < active_hooks.array.length; i++) {
+ if (typeof active_hooks.array[i][symbol] === 'function') {
+ active_hooks.array[i][symbol](asyncId);
+ }
+ }
+ } catch (e) {
+ fatalError(e);
+ } finally {
+ active_hooks.call_depth -= 1;
+ }
+
+ // Hooks can only be restored if there have been no recursive hook calls.
+ // Also the active hooks do not need to be restored if enable()/disable()
+ // weren't called during hook execution, in which case
+ // active_hooks.tmp_array will be null.
+ if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
+ restoreActiveHooks();
+ }
+ };
+
+ // Set the name property of the anonymous function as it looks good in the
+ // stack trace.
+ Object.defineProperty(fn, 'name', {
+ value: name
+ });
+ return fn;
+}
+
+// Manage Active Hooks //
+
+function getHookArrays() {
+ if (active_hooks.call_depth === 0)
+ return [active_hooks.array, async_hook_fields];
+ // If this hook is being enabled while in the middle of processing the array
+ // of currently active hooks then duplicate the current set of active hooks
+ // and store this there. This shouldn't fire until the next time hooks are
+ // processed.
+ if (active_hooks.tmp_array === null)
+ storeActiveHooks();
+ return [active_hooks.tmp_array, active_hooks.tmp_fields];
+}
+
+
+function storeActiveHooks() {
+ active_hooks.tmp_array = active_hooks.array.slice();
+ // Don't want to make the assumption that kInit to kDestroy are indexes 0 to
+ // 4. So do this the long way.
+ active_hooks.tmp_fields = [];
+ active_hooks.tmp_fields[kInit] = async_hook_fields[kInit];
+ active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
+ active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
+ active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
+ active_hooks.tmp_fields[kPromiseResolve] = async_hook_fields[kPromiseResolve];
+}
+
+
+// Then restore the correct hooks array in case any hooks were added/removed
+// during hook callback execution.
+function restoreActiveHooks() {
+ active_hooks.array = active_hooks.tmp_array;
+ async_hook_fields[kInit] = active_hooks.tmp_fields[kInit];
+ async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
+ async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
+ async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
+ async_hook_fields[kPromiseResolve] = active_hooks.tmp_fields[kPromiseResolve];
+
+ active_hooks.tmp_array = null;
+ active_hooks.tmp_fields = null;
+}
+
+
+function enableHooks() {
+ enablePromiseHook();
+ async_hook_fields[kCheck] += 1;
+}
+
+function disableHooks() {
+ disablePromiseHook();
+ async_hook_fields[kCheck] -= 1;
+}
+
+// Sensitive Embedder API //
+
+// Increment the internal id counter and return the value. Important that the
+// counter increment first. Since it's done the same way in
+// Environment::new_async_uid()
+function newUid() {
+ return ++async_id_fields[kAsyncIdCounter];
+}
+
+
+// Return the triggerAsyncId meant for the constructor calling it. It's up to
+// the user to safeguard this call and make sure it's zero'd out when the
+// constructor is complete.
+function initTriggerId() {
+ var triggerAsyncId = async_id_fields[kInitTriggerAsyncId];
+ // Reset value after it's been called so the next constructor doesn't
+ // inherit it by accident.
+ async_id_fields[kInitTriggerAsyncId] = 0;
+ if (triggerAsyncId <= 0)
+ triggerAsyncId = async_id_fields[kExecutionAsyncId];
+ return triggerAsyncId;
+}
+
+
+function setInitTriggerId(triggerAsyncId) {
+ // CHECK(Number.isSafeInteger(triggerAsyncId))
+ // CHECK(triggerAsyncId > 0)
+ async_id_fields[kInitTriggerAsyncId] = triggerAsyncId;
+}
+
+
+function emitInitScript(asyncId, type, triggerAsyncId, resource) {
+ validateAsyncId(asyncId, 'asyncId');
+ if (triggerAsyncId !== null)
+ validateAsyncId(triggerAsyncId, 'triggerAsyncId');
+ if (async_hook_fields[kCheck] > 0 &&
+ (typeof type !== 'string' || type.length <= 0)) {
+ throw new errors.TypeError('ERR_ASYNC_TYPE', type);
+ }
+
+ // Short circuit all checks for the common case. Which is that no hooks have
+ // been set. Do this to remove performance impact for embedders (and core).
+ if (async_hook_fields[kInit] === 0)
+ return;
+
+ // This can run after the early return check b/c running this function
+ // manually means that the embedder must have used initTriggerId().
+ if (triggerAsyncId === null) {
+ triggerAsyncId = initTriggerId();
+ } else {
+ // If a triggerAsyncId was passed, any kInitTriggerAsyncId still must be
+ // null'd.
+ async_id_fields[kInitTriggerAsyncId] = 0;
+ }
+
+ emitInitNative(asyncId, type, triggerAsyncId, resource);
+}
+
+
+function emitBeforeScript(asyncId, triggerAsyncId) {
+ // Validate the ids. An id of -1 means it was never set and is visible on the
+ // call graph. An id < -1 should never happen in any circumstance. Throw
+ // on user calls because async state should still be recoverable.
+ validateAsyncId(asyncId, 'asyncId');
+ validateAsyncId(triggerAsyncId, 'triggerAsyncId');
+
+ pushAsyncIds(asyncId, triggerAsyncId);
+
+ if (async_hook_fields[kBefore] > 0)
+ emitBeforeNative(asyncId);
+}
+
+
+function emitAfterScript(asyncId) {
+ validateAsyncId(asyncId, 'asyncId');
+
+ if (async_hook_fields[kAfter] > 0)
+ emitAfterNative(asyncId);
+
+ popAsyncIds(asyncId);
+}
+
+
+function emitDestroyScript(asyncId) {
+ validateAsyncId(asyncId, 'asyncId');
+
+ // Return early if there are no destroy callbacks, or invalid asyncId.
+ if (async_hook_fields[kDestroy] === 0 || asyncId <= 0)
+ return;
+ async_wrap.queueDestroyAsyncId(asyncId);
+}
+
+
+module.exports = {
+ // Private API
+ getHookArrays,
+ symbols: {
+ init_symbol, before_symbol, after_symbol, destroy_symbol,
+ promise_resolve_symbol
+ },
+ enableHooks,
+ disableHooks,
+ // Sensitive Embedder API
+ newUid,
+ initTriggerId,
+ setInitTriggerId,
+ emitInit: emitInitScript,
+ emitBefore: emitBeforeScript,
+ emitAfter: emitAfterScript,
+ emitDestroy: emitDestroyScript,
+};
diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js
index b86caf61a5c..fb8bc762286 100644
--- a/lib/internal/bootstrap_node.js
+++ b/lib/internal/bootstrap_node.js
@@ -401,7 +401,7 @@
// Emit the after() hooks now that the exception has been handled.
if (async_hook_fields[kAfter] > 0) {
do {
- NativeModule.require('async_hooks').emitAfter(
+ NativeModule.require('internal/async_hooks').emitAfter(
async_id_fields[kExecutionAsyncId]);
} while (asyncIdStackSize() > 0);
// Or completely empty the id stack.
diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js
index 83a833dc596..01c011d12c8 100644
--- a/lib/internal/process/next_tick.js
+++ b/lib/internal/process/next_tick.js
@@ -48,7 +48,7 @@ class NextTickQueue {
function setupNextTick() {
const async_wrap = process.binding('async_wrap');
- const async_hooks = require('async_hooks');
+ const async_hooks = require('internal/async_hooks');
const promises = require('internal/process/promises');
const errors = require('internal/errors');
const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks);
diff --git a/lib/net.js b/lib/net.js
index 93b2279c7b1..1d64eb3c56b 100644
--- a/lib/net.js
+++ b/lib/net.js
@@ -43,7 +43,7 @@ const { TCPConnectWrap } = process.binding('tcp_wrap');
const { PipeConnectWrap } = process.binding('pipe_wrap');
const { ShutdownWrap, WriteWrap } = process.binding('stream_wrap');
const { async_id_symbol } = process.binding('async_wrap');
-const { newUid, setInitTriggerId } = require('async_hooks');
+const { newUid, setInitTriggerId } = require('internal/async_hooks');
const { nextTick } = require('internal/process/next_tick');
const errors = require('internal/errors');
const dns = require('dns');
diff --git a/lib/timers.js b/lib/timers.js
index 56f02fe1ea6..3c522e76f1a 100644
--- a/lib/timers.js
+++ b/lib/timers.js
@@ -40,7 +40,7 @@ const {
emitBefore,
emitAfter,
emitDestroy
-} = require('async_hooks');
+} = require('internal/async_hooks');
// Grab the constants necessary for working with internal arrays.
const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
// Symbols for storing async id state.
diff --git a/node.gyp b/node.gyp
index 43a6576206d..86a4331a4ed 100644
--- a/node.gyp
+++ b/node.gyp
@@ -76,6 +76,7 @@
'lib/v8.js',
'lib/vm.js',
'lib/zlib.js',
+ 'lib/internal/async_hooks.js',
'lib/internal/buffer.js',
'lib/internal/child_process.js',
'lib/internal/cluster/child.js',
diff --git a/test/async-hooks/test-callback-error.js b/test/async-hooks/test-callback-error.js
index b2093f74e28..c45856f60da 100644
--- a/test/async-hooks/test-callback-error.js
+++ b/test/async-hooks/test-callback-error.js
@@ -11,35 +11,22 @@ switch (arg) {
initHooks({
oninit: common.mustCall(() => { throw new Error(arg); })
}).enable();
- async_hooks.emitInit(
- async_hooks.newUid(),
- `${arg}_type`,
- async_hooks.executionAsyncId()
- );
+ new async_hooks.AsyncResource(`${arg}_type`);
return;
case 'test_callback':
initHooks({
onbefore: common.mustCall(() => { throw new Error(arg); })
}).enable();
- const newAsyncId = async_hooks.newUid();
- async_hooks.emitInit(
- newAsyncId,
- `${arg}_type`,
- async_hooks.executionAsyncId()
- );
- async_hooks.emitBefore(newAsyncId, async_hooks.executionAsyncId());
+ const resource = new async_hooks.AsyncResource(`${arg}_type`);
+ resource.emitBefore();
return;
case 'test_callback_abort':
initHooks({
oninit: common.mustCall(() => { throw new Error(arg); })
}).enable();
- async_hooks.emitInit(
- async_hooks.newUid(),
- `${arg}_type`,
- async_hooks.executionAsyncId()
- );
+ new async_hooks.AsyncResource(`${arg}_type`);
return;
}
diff --git a/test/async-hooks/test-emit-before-after.js b/test/async-hooks/test-emit-before-after.js
index 2b22739fa94..1b28c1e4262 100644
--- a/test/async-hooks/test-emit-before-after.js
+++ b/test/async-hooks/test-emit-before-after.js
@@ -1,9 +1,10 @@
'use strict';
+// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const spawnSync = require('child_process').spawnSync;
-const async_hooks = require('async_hooks');
+const async_hooks = require('internal/async_hooks');
const initHooks = require('./init-hooks');
switch (process.argv[2]) {
@@ -17,13 +18,17 @@ switch (process.argv[2]) {
assert.ok(!process.argv[2]);
-const c1 = spawnSync(process.execPath, [__filename, 'test_invalid_async_id']);
+const c1 = spawnSync(process.execPath, [
+ '--expose-internals', __filename, 'test_invalid_async_id'
+]);
assert.strictEqual(
c1.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: -2');
assert.strictEqual(c1.status, 1);
-const c2 = spawnSync(process.execPath, [__filename, 'test_invalid_trigger_id']);
+const c2 = spawnSync(process.execPath, [
+ '--expose-internals', __filename, 'test_invalid_trigger_id'
+]);
assert.strictEqual(
c2.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: -2');
diff --git a/test/async-hooks/test-emit-init.js b/test/async-hooks/test-emit-init.js
index 9c61f19dab7..e69285d4f81 100644
--- a/test/async-hooks/test-emit-init.js
+++ b/test/async-hooks/test-emit-init.js
@@ -1,9 +1,10 @@
'use strict';
+// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const spawnSync = require('child_process').spawnSync;
-const async_hooks = require('async_hooks');
+const async_hooks = require('internal/async_hooks');
const initHooks = require('./init-hooks');
const expectedId = async_hooks.newUid();
@@ -36,20 +37,24 @@ switch (process.argv[2]) {
assert.ok(!process.argv[2]);
-const c1 = spawnSync(process.execPath, [__filename, 'test_invalid_async_id']);
+const c1 = spawnSync(process.execPath, [
+ '--expose-internals', __filename, 'test_invalid_async_id'
+]);
assert.strictEqual(
c1.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: undefined');
assert.strictEqual(c1.status, 1);
-const c2 = spawnSync(process.execPath, [__filename, 'test_invalid_trigger_id']);
+const c2 = spawnSync(process.execPath, [
+ '--expose-internals', __filename, 'test_invalid_trigger_id'
+]);
assert.strictEqual(
c2.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: undefined');
assert.strictEqual(c2.status, 1);
const c3 = spawnSync(process.execPath, [
- __filename, 'test_invalid_trigger_id_negative'
+ '--expose-internals', __filename, 'test_invalid_trigger_id_negative'
]);
assert.strictEqual(
c3.stderr.toString().split(/[\r\n]+/g)[0],
diff --git a/test/parallel/test-async-hooks-run-in-async-id-scope.js b/test/parallel/test-async-hooks-run-in-async-id-scope.js
index 8cef7d214c2..14d1c7423fc 100644
--- a/test/parallel/test-async-hooks-run-in-async-id-scope.js
+++ b/test/parallel/test-async-hooks-run-in-async-id-scope.js
@@ -4,7 +4,7 @@ const common = require('../common');
const assert = require('assert');
const async_hooks = require('async_hooks');
-const asyncId = async_hooks.newUid();
+const asyncId = new async_hooks.AsyncResource('test').asyncId();
assert.notStrictEqual(async_hooks.executionAsyncId(), asyncId);
diff --git a/test/parallel/test-http-client-immediate-error.js b/test/parallel/test-http-client-immediate-error.js
index 6b9cacb256f..abbf5c41fc6 100644
--- a/test/parallel/test-http-client-immediate-error.js
+++ b/test/parallel/test-http-client-immediate-error.js
@@ -1,4 +1,5 @@
'use strict';
+// Flags: --expose-internals
// Make sure http.request() can catch immediate errors in
// net.createConnection().
@@ -9,7 +10,7 @@ const net = require('net');
const http = require('http');
const uv = process.binding('uv');
const { async_id_symbol } = process.binding('async_wrap');
-const { newUid } = require('async_hooks');
+const { newUid } = require('internal/async_hooks');
const agent = new http.Agent();
agent.createConnection = common.mustCall((cfg) => {