Skip to content

Commit

Permalink
lib: runtime deprecate access to process.binding('async_wrap')
Browse files Browse the repository at this point in the history
Signed-off-by: James M Snell <jasnell@gmail.com>
  • Loading branch information
jasnell committed Mar 2, 2021
1 parent 2748b0a commit d68a7af
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 76 deletions.
12 changes: 12 additions & 0 deletions doc/api/async_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ provided by AsyncHooks itself. The logging should then be skipped when
it was the logging itself that caused AsyncHooks callback to call. By
doing this the otherwise infinite recursion is broken.

#### `async_hooks.Providers`
<!-- YAML
added: REPLACEME
-->

* Type: {Object}

An object whose properties list Node.js-provided async resource types and
their associated internal numeric identifiers. This is provided as a
convenience for diagnostic tooling. The names and numeric identifiers of
any provider may be changed at any time.

### Class: `AsyncHook`

The class `AsyncHook` exposes an interface for tracking lifetime events
Expand Down
48 changes: 28 additions & 20 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ const {
} = primordials;

const {
ERR_ASYNC_CALLBACK,
ERR_ASYNC_TYPE,
ERR_INVALID_ASYNC_ID
} = require('internal/errors').codes;
codes: {
ERR_ASYNC_CALLBACK,
ERR_ASYNC_TYPE,
ERR_INVALID_ASYNC_ID,
},
} = require('internal/errors');

const {
validateFunction,
validateString,
} = require('internal/validators');
const internal_async_hooks = require('internal/async_hooks');

// Get functions
// For userland AsyncResources, make sure to emit a destroy event when the
// resource gets gced.
const { registerDestroyHook } = internal_async_hooks;
const {
executionAsyncId,
triggerAsyncId,
Expand All @@ -38,6 +38,7 @@ const {
disableHooks,
updatePromiseHookMode,
executionAsyncResource,
registerDestroyHook,
// Internal Embedder API
newAsyncId,
getDefaultTriggerAsyncId,
Expand All @@ -48,19 +49,25 @@ const {
enabledHooksExist,
initHooksExist,
destroyHooksExist,
} = internal_async_hooks;

// Get symbols
const {
async_id_symbol, trigger_async_id_symbol,
init_symbol, before_symbol, after_symbol, destroy_symbol,
promise_resolve_symbol
} = internal_async_hooks.symbols;

// Get constants
const {
kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
} = internal_async_hooks.constants;
symbols: {
async_id_symbol,
trigger_async_id_symbol,
init_symbol,
before_symbol,
after_symbol,
destroy_symbol,
promise_resolve_symbol
},
constants: {
kInit,
kBefore,
kAfter,
kDestroy,
kTotals,
kPromiseResolve,
},
Providers,
} = require('internal/async_hooks');

// Listener API //

Expand Down Expand Up @@ -350,4 +357,5 @@ module.exports = {
executionAsyncResource,
// Embedder API
AsyncResource,
Providers,
};
128 changes: 72 additions & 56 deletions lib/internal/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,72 @@ const {
Symbol,
} = primordials;

const async_wrap = internalBinding('async_wrap');
const { setCallbackTrampoline } = 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 async_id of the resource that caused (or 'triggered')
* the resource corresponding to the current execution stack.
* kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
* kDefaultTriggerAsyncId: Written immediately before a resource's constructor
* that sets the value of the init()'s triggerAsyncId. The precedence order
* of retrieving the triggerAsyncId value is:
* 1. the value passed directly to the constructor
* 2. value set in kDefaultTriggerAsyncId
* 3. executionAsyncId of the current resource.
*
* async_ids_stack is a Float64Array that contains part of the async ID
* stack. Each pushAsyncContext() call adds two doubles to it, and each
* popAsyncContext() call removes two doubles from it.
* It has a fixed size, so if that is exceeded, calls to the native
* side are used instead in pushAsyncContext() and popAsyncContext().
*/
// 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 {
setCallbackTrampoline,
queueDestroyAsyncId,
async_ids_stack,
async_hook_fields,
async_id_fields,
execution_async_resources
} = async_wrap;
execution_async_resources,
pushAsyncContext: pushAsyncContext_,
popAsyncContext: popAsyncContext_,
executionAsyncResource: executionAsyncResource_,
clearAsyncIdStack,
enablePromiseHook,
disablePromiseHook,
registerDestroyHook,
Providers,
constants: {
kInit,
kBefore,
kAfter,
kDestroy,
kTotals,
kPromiseResolve,
kCheck,
kExecutionAsyncId,
kAsyncIdCounter,
kTriggerAsyncId,
kDefaultTriggerAsyncId,
kStackLength,
kUsesExecutionAsyncResource,
},
} = internalBinding('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 async_id of the resource that caused (or 'triggered')
// the resource corresponding to the current execution stack.
// kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
// kDefaultTriggerAsyncId: Written immediately before a resource's constructor
// that sets the value of the init()'s triggerAsyncId. The precedence order
// of retrieving the triggerAsyncId value is:
// 1. the value passed directly to the constructor
// 2. value set in kDefaultTriggerAsyncId
// 3. executionAsyncId of the current resource.
//
// async_ids_stack is a Float64Array that contains part of the async ID
// stack. Each pushAsyncContext() call adds two doubles to it, and each
// popAsyncContext() call removes two doubles from it.
// It has a fixed size, so if that is exceeded, calls to the native
// side are used instead in pushAsyncContext() and popAsyncContext().

// Store the pair executionAsyncId and triggerAsyncId in a AliasedFloat64Array
// in Environment::AsyncHooks::async_ids_stack_ which 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 {
pushAsyncContext: pushAsyncContext_,
popAsyncContext: popAsyncContext_,
executionAsyncResource: executionAsyncResource_,
clearAsyncIdStack,
} = async_wrap;
// For performance reasons, only track Promises 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.
Expand All @@ -84,21 +105,14 @@ const active_hooks = {
tmp_fields: null
};

const { registerDestroyHook } = async_wrap;
const { enqueueMicrotask } = internalBinding('task_queue');
const { resource_symbol, owner_symbol } = internalBinding('symbols');

// 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, kAsyncIdCounter, kTriggerAsyncId,
kDefaultTriggerAsyncId, kStackLength, kUsesExecutionAsyncResource,
} = async_wrap.constants;

const { async_id_symbol,
trigger_async_id_symbol } = internalBinding('symbols');
async_id_symbol,
resource_symbol,
owner_symbol,
trigger_async_id_symbol,
} = internalBinding('symbols');

// Used in AsyncHook and AsyncResource.
const init_symbol = Symbol('init');
Expand Down Expand Up @@ -489,7 +503,7 @@ function emitDestroyScript(asyncId) {
// Return early if there are no destroy callbacks, or invalid asyncId.
if (!hasHooks(kDestroy) || asyncId <= 0)
return;
async_wrap.queueDestroyAsyncId(asyncId);
queueDestroyAsyncId(asyncId);
}


Expand All @@ -502,10 +516,10 @@ function hasAsyncIdStack() {
function pushAsyncContext(asyncId, triggerAsyncId, resource) {
const offset = async_hook_fields[kStackLength];
execution_async_resources[offset] = resource;
if (offset * 2 >= async_wrap.async_ids_stack.length)
if (offset * 2 >= async_ids_stack.length)
return pushAsyncContext_(asyncId, triggerAsyncId);
async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
async_hook_fields[kStackLength]++;
async_id_fields[kExecutionAsyncId] = asyncId;
async_id_fields[kTriggerAsyncId] = triggerAsyncId;
Expand All @@ -523,8 +537,8 @@ function popAsyncContext(asyncId) {
}

const offset = stackLength - 1;
async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
async_id_fields[kExecutionAsyncId] = async_ids_stack[2 * offset];
async_id_fields[kTriggerAsyncId] = async_ids_stack[2 * offset + 1];
ArrayPrototypePop(execution_async_resources);
async_hook_fields[kStackLength] = offset;
return offset > 0;
Expand Down Expand Up @@ -583,5 +597,7 @@ module.exports = {
after: emitAfterNative,
destroy: emitDestroyNative,
promise_resolve: emitPromiseResolveNative
}
},
// Export a copy so that the originals are never changed.
Providers: { ...Providers },
};
11 changes: 11 additions & 0 deletions lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ const internalBindingAllowlist = new SafeSet([
'zlib',
]);

const runtimeDeprecatedList = new SafeSet([
'async_wrap',
]);

// Set up process.binding() and process._linkedBinding().
{
const bindingObj = ObjectCreate(null);
Expand All @@ -114,6 +118,13 @@ const internalBindingAllowlist = new SafeSet([
// Deprecated specific process.binding() modules, but not all, allow
// selective fallback to internalBinding for the deprecated ones.
if (internalBindingAllowlist.has(module)) {
if (runtimeDeprecatedList.has(module)) {
runtimeDeprecatedList.delete(module);
process.emitWarning(
`Access to process.binding('${module}') is deprecated.`,
'DeprecationWarning',
'DEP0111');
}
return internalBinding(module);
}
// eslint-disable-next-line no-restricted-syntax
Expand Down

0 comments on commit d68a7af

Please sign in to comment.