async_hooks: deprecate undocumented API

PR-URL: https://github.com/nodejs/node/pull/16972
Refs: https://github.com/nodejs/node/issues/14328
Refs: https://github.com/nodejs/node/issues/15572
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Andreas Madsen 2017-11-12 18:46:55 +01:00
parent 97ba69f915
commit 07a4fa3dbe
No known key found for this signature in database
GPG Key ID: 2FEE61B3C9E40F20
14 changed files with 496 additions and 363 deletions

View File

@ -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 manager, as it is published on the npm registry under the same name. No source
code modification is necessary if that is done. code modification is necessary if that is done.
<a id="DEP0085"></a>
### 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.
<a id="DEP0086"></a>
### 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.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
[`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array [`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array

View File

@ -1,118 +1,50 @@
'use strict'; 'use strict';
const async_wrap = process.binding('async_wrap');
const errors = require('internal/errors'); const errors = require('internal/errors');
/* async_hook_fields is a Uint32Array wrapping the uint32_t array of const internalUtil = require('internal/util');
* Environment::AsyncHooks::fields_[]. Each index tracks the number of active const async_wrap = process.binding('async_wrap');
* hooks for each type. const internal_async_hooks = require('internal/async_hooks');
*
* async_id_fields is a Float64Array wrapping the double array of // Get functions
* Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for // Only used to support a deprecated API. pushAsyncIds, popAsyncIds should
* the various asynchronous states of the application. These are: // never be directly in this manner.
* 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; 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 // For userland AsyncResources, make sure to emit a destroy event when the
// resource gets gced. // resource gets gced.
const { registerDestroyHook } = async_wrap; const { registerDestroyHook } = async_wrap;
// Properties in active_hooks are used to keep track of the set of hooks being const {
// executed in case another hook is enabled/disabled. The new set of hooks is // Private API
// then restored once the active set of hooks is finished executing. getHookArrays,
const active_hooks = { enableHooks,
// Array of all AsyncHooks that will be iterated whenever an async event disableHooks,
// fires. Using var instead of (preferably const) in order to assign // Sensitive Embedder API
// active_hooks.tmp_array if a hook is enabled/disabled during hook newUid,
// execution. initTriggerId,
array: [], setInitTriggerId,
// Use a counter to track nested calls of async hook callbacks and make sure emitInit,
// the active_hooks.array isn't altered mid execution. emitBefore,
call_depth: 0, emitAfter,
// Use to temporarily store and updated active_hooks.array if the user emitDestroy,
// enables or disables a hook while hooks are being processed. If a hook is } = internal_async_hooks;
// 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
};
// Get fields
const { async_id_fields } = async_wrap;
// Each constant tracks how many callbacks there are for any given step of // Get symbols
// async execution. These are tracked so if the user didn't include callbacks const {
// for a given step, that step can bail out early. init_symbol, before_symbol, after_symbol, destroy_symbol,
const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve, promise_resolve_symbol
kCheck, kExecutionAsyncId, kTriggerAsyncId, kAsyncIdCounter, } = internal_async_hooks.symbols;
kInitTriggerAsyncId } = async_wrap.constants;
// 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; const { async_id_symbol, trigger_async_id_symbol } = async_wrap;
// Used in AsyncHook and AsyncResource. // Get constants
const init_symbol = Symbol('init'); const {
const before_symbol = Symbol('before'); kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
const after_symbol = Symbol('after'); kExecutionAsyncId, kTriggerAsyncId
const destroy_symbol = Symbol('destroy'); } = async_wrap.constants;
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 // Listener API //
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 //
class AsyncHook { class AsyncHook {
constructor({ init, before, after, destroy, promiseResolve }) { constructor({ init, before, after, destroy, promiseResolve }) {
@ -160,8 +92,7 @@ class AsyncHook {
hooks_array.push(this); hooks_array.push(this);
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) { if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
enablePromiseHook(); enableHooks();
hook_fields[kCheck] += 1;
} }
return this; return this;
@ -186,8 +117,7 @@ class AsyncHook {
hooks_array.splice(index, 1); hooks_array.splice(index, 1);
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) { if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
disablePromiseHook(); disableHooks();
hook_fields[kCheck] -= 1;
} }
return this; 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) { function createHook(fns) {
return new AsyncHook(fns); return new AsyncHook(fns);
} }
@ -250,15 +139,6 @@ function triggerAsyncId() {
return async_id_fields[kTriggerAsyncId]; 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 // // Embedder API //
@ -284,12 +164,12 @@ class AsyncResource {
triggerAsyncId); triggerAsyncId);
} }
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter]; this[async_id_symbol] = newUid();
this[trigger_async_id_symbol] = triggerAsyncId; this[trigger_async_id_symbol] = triggerAsyncId;
// this prop name (destroyed) has to be synchronized with C++ // this prop name (destroyed) has to be synchronized with C++
this[destroyedSymbol] = { destroyed: false }; this[destroyedSymbol] = { destroyed: false };
emitInitScript( emitInit(
this[async_id_symbol], type, this[trigger_async_id_symbol], this this[async_id_symbol], type, this[trigger_async_id_symbol], this
); );
@ -299,18 +179,18 @@ class AsyncResource {
} }
emitBefore() { emitBefore() {
emitBeforeScript(this[async_id_symbol], this[trigger_async_id_symbol]); emitBefore(this[async_id_symbol], this[trigger_async_id_symbol]);
return this; return this;
} }
emitAfter() { emitAfter() {
emitAfterScript(this[async_id_symbol]); emitAfter(this[async_id_symbol]);
return this; return this;
} }
emitDestroy() { emitDestroy() {
this[destroyedSymbol].destroyed = true; this[destroyedSymbol].destroyed = true;
emitDestroyScript(this[async_id_symbol]); emitDestroy(this[async_id_symbol]);
return this; 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 // Placing all exports down here because the exported classes won't export
// otherwise. // otherwise.
module.exports = { module.exports = {
@ -507,13 +225,62 @@ module.exports = {
triggerAsyncId, triggerAsyncId,
// Embedder API // Embedder API
AsyncResource, 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')
});

View File

@ -28,7 +28,7 @@ const dns = require('dns');
const util = require('util'); const util = require('util');
const { isUint8Array } = require('internal/util/types'); const { isUint8Array } = require('internal/util/types');
const EventEmitter = require('events'); const EventEmitter = require('events');
const { setInitTriggerId } = require('async_hooks'); const { setInitTriggerId } = require('internal/async_hooks');
const { UV_UDP_REUSEADDR } = process.binding('constants').os; const { UV_UDP_REUSEADDR } = process.binding('constants').os;
const { async_id_symbol } = process.binding('async_wrap'); const { async_id_symbol } = process.binding('async_wrap');
const { nextTick } = require('internal/process/next_tick'); const { nextTick } = require('internal/process/next_tick');

349
lib/internal/async_hooks.js Normal file
View File

@ -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,
};

View File

@ -401,7 +401,7 @@
// Emit the after() hooks now that the exception has been handled. // Emit the after() hooks now that the exception has been handled.
if (async_hook_fields[kAfter] > 0) { if (async_hook_fields[kAfter] > 0) {
do { do {
NativeModule.require('async_hooks').emitAfter( NativeModule.require('internal/async_hooks').emitAfter(
async_id_fields[kExecutionAsyncId]); async_id_fields[kExecutionAsyncId]);
} while (asyncIdStackSize() > 0); } while (asyncIdStackSize() > 0);
// Or completely empty the id stack. // Or completely empty the id stack.

View File

@ -48,7 +48,7 @@ class NextTickQueue {
function setupNextTick() { function setupNextTick() {
const async_wrap = process.binding('async_wrap'); 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 promises = require('internal/process/promises');
const errors = require('internal/errors'); const errors = require('internal/errors');
const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks);

View File

@ -43,7 +43,7 @@ const { TCPConnectWrap } = process.binding('tcp_wrap');
const { PipeConnectWrap } = process.binding('pipe_wrap'); const { PipeConnectWrap } = process.binding('pipe_wrap');
const { ShutdownWrap, WriteWrap } = process.binding('stream_wrap'); const { ShutdownWrap, WriteWrap } = process.binding('stream_wrap');
const { async_id_symbol } = process.binding('async_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 { nextTick } = require('internal/process/next_tick');
const errors = require('internal/errors'); const errors = require('internal/errors');
const dns = require('dns'); const dns = require('dns');

View File

@ -40,7 +40,7 @@ const {
emitBefore, emitBefore,
emitAfter, emitAfter,
emitDestroy emitDestroy
} = require('async_hooks'); } = require('internal/async_hooks');
// Grab the constants necessary for working with internal arrays. // Grab the constants necessary for working with internal arrays.
const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants; const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
// Symbols for storing async id state. // Symbols for storing async id state.

View File

@ -76,6 +76,7 @@
'lib/v8.js', 'lib/v8.js',
'lib/vm.js', 'lib/vm.js',
'lib/zlib.js', 'lib/zlib.js',
'lib/internal/async_hooks.js',
'lib/internal/buffer.js', 'lib/internal/buffer.js',
'lib/internal/child_process.js', 'lib/internal/child_process.js',
'lib/internal/cluster/child.js', 'lib/internal/cluster/child.js',

View File

@ -11,35 +11,22 @@ switch (arg) {
initHooks({ initHooks({
oninit: common.mustCall(() => { throw new Error(arg); }) oninit: common.mustCall(() => { throw new Error(arg); })
}).enable(); }).enable();
async_hooks.emitInit( new async_hooks.AsyncResource(`${arg}_type`);
async_hooks.newUid(),
`${arg}_type`,
async_hooks.executionAsyncId()
);
return; return;
case 'test_callback': case 'test_callback':
initHooks({ initHooks({
onbefore: common.mustCall(() => { throw new Error(arg); }) onbefore: common.mustCall(() => { throw new Error(arg); })
}).enable(); }).enable();
const newAsyncId = async_hooks.newUid(); const resource = new async_hooks.AsyncResource(`${arg}_type`);
async_hooks.emitInit( resource.emitBefore();
newAsyncId,
`${arg}_type`,
async_hooks.executionAsyncId()
);
async_hooks.emitBefore(newAsyncId, async_hooks.executionAsyncId());
return; return;
case 'test_callback_abort': case 'test_callback_abort':
initHooks({ initHooks({
oninit: common.mustCall(() => { throw new Error(arg); }) oninit: common.mustCall(() => { throw new Error(arg); })
}).enable(); }).enable();
async_hooks.emitInit( new async_hooks.AsyncResource(`${arg}_type`);
async_hooks.newUid(),
`${arg}_type`,
async_hooks.executionAsyncId()
);
return; return;
} }

View File

@ -1,9 +1,10 @@
'use strict'; 'use strict';
// Flags: --expose-internals
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const spawnSync = require('child_process').spawnSync; 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 initHooks = require('./init-hooks');
switch (process.argv[2]) { switch (process.argv[2]) {
@ -17,13 +18,17 @@ switch (process.argv[2]) {
assert.ok(!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( assert.strictEqual(
c1.stderr.toString().split(/[\r\n]+/g)[0], c1.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: -2'); 'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: -2');
assert.strictEqual(c1.status, 1); 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( assert.strictEqual(
c2.stderr.toString().split(/[\r\n]+/g)[0], c2.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: -2'); 'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: -2');

View File

@ -1,9 +1,10 @@
'use strict'; 'use strict';
// Flags: --expose-internals
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const spawnSync = require('child_process').spawnSync; 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 initHooks = require('./init-hooks');
const expectedId = async_hooks.newUid(); const expectedId = async_hooks.newUid();
@ -36,20 +37,24 @@ switch (process.argv[2]) {
assert.ok(!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( assert.strictEqual(
c1.stderr.toString().split(/[\r\n]+/g)[0], c1.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: undefined'); 'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: undefined');
assert.strictEqual(c1.status, 1); 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( assert.strictEqual(
c2.stderr.toString().split(/[\r\n]+/g)[0], c2.stderr.toString().split(/[\r\n]+/g)[0],
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: undefined'); 'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: undefined');
assert.strictEqual(c2.status, 1); assert.strictEqual(c2.status, 1);
const c3 = spawnSync(process.execPath, [ const c3 = spawnSync(process.execPath, [
__filename, 'test_invalid_trigger_id_negative' '--expose-internals', __filename, 'test_invalid_trigger_id_negative'
]); ]);
assert.strictEqual( assert.strictEqual(
c3.stderr.toString().split(/[\r\n]+/g)[0], c3.stderr.toString().split(/[\r\n]+/g)[0],

View File

@ -4,7 +4,7 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const async_hooks = require('async_hooks'); 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); assert.notStrictEqual(async_hooks.executionAsyncId(), asyncId);

View File

@ -1,4 +1,5 @@
'use strict'; 'use strict';
// Flags: --expose-internals
// Make sure http.request() can catch immediate errors in // Make sure http.request() can catch immediate errors in
// net.createConnection(). // net.createConnection().
@ -9,7 +10,7 @@ const net = require('net');
const http = require('http'); const http = require('http');
const uv = process.binding('uv'); const uv = process.binding('uv');
const { async_id_symbol } = process.binding('async_wrap'); const { async_id_symbol } = process.binding('async_wrap');
const { newUid } = require('async_hooks'); const { newUid } = require('internal/async_hooks');
const agent = new http.Agent(); const agent = new http.Agent();
agent.createConnection = common.mustCall((cfg) => { agent.createConnection = common.mustCall((cfg) => {