Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,46 @@ Error: Cannot load native addon because loading addons is disabled.
}
```

### `--allow-async-hooks`

<!-- YAML
added: REPLACEME
-->

Allow use of `async_hooks.createHook()`. This flag is required to enable
the `createHook` API, which is disabled by default.

Example:

```mjs
import { createHook } from 'node:async_hooks';

createHook({ init() {} }).enable();
```

```cjs
const { createHook } = require('node:async_hooks');

createHook({ init() {} }).enable();
```

```console
$ node index.js
node:async_hooks:160
throw new ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED();
^

Error [ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED]: async_hooks.createHook() is disabled. Use --allow-async-hooks to enable it.
at createHook (node:async_hooks:160:11)
at Object.<anonymous> (/home/user/index.js:3:1)
...
```

```console
$ node --allow-async-hooks index.js
# Works without error
```

### `--allow-child-process`

<!-- YAML
Expand Down Expand Up @@ -4233,6 +4273,7 @@ one is included in the list below.
<!-- node-options-node start -->

* `--allow-addons`
* `--allow-async-hooks`
* `--allow-child-process`
* `--allow-env`
* `--allow-ffi`
Expand Down
13 changes: 13 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,17 @@ by the `node:assert` module.
An attempt was made to register something that is not a function as an
`AsyncHooks` callback.

<a id="ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED"></a>

### `ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED`

<!-- YAML
added: REPLACEME
-->

An attempt was made to call [`async_hooks.createHook()`][] without the
[`--allow-async-hooks`][] flag.

<a id="ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED"></a>

### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED`
Expand Down Expand Up @@ -4770,6 +4781,7 @@ An error occurred trying to allocate memory. This should never happen.
[`"exports"`]: packages.md#exports
[`"imports"`]: packages.md#imports
[`'uncaughtException'`]: process.md#event-uncaughtexception
[`--allow-async-hooks`]: cli.md#--allow-async-hooks
[`--disable-proto=throw`]: cli.md#--disable-protomode
[`--force-fips`]: cli.md#--force-fips
[`--no-addons`]: cli.md#--no-addons
Expand All @@ -4795,6 +4807,7 @@ An error occurred trying to allocate memory. This should never happen.
[`Writable`]: stream.md#class-streamwritable
[`ZipBuffer`]: zlib.md#class-zlibzipbuffer
[`ZipFile`]: zlib.md#class-zlibzipfile
[`async_hooks.createHook()`]: async_hooks.md#async_hookscreatehookoptions
[`child_process`]: child_process.md
[`cipher.getAuthTag()`]: crypto.md#ciphergetauthtag
[`crypto.getDiffieHellman()`]: crypto.md#cryptogetdiffiehellmangroupname
Expand Down
32 changes: 32 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,36 @@ Error: Cannot load native addon because loading addons is disabled.
}
.Ed
.
.It Fl -allow-async-hooks
Allow use of \fBasync_hooks.createHook()\fR. This flag is required to enable
the \fBcreateHook\fR API, which is disabled by default.
Example:
.Bd -literal
import { createHook } from 'node:async_hooks';

createHook({ init() {} }).enable();
.Ed
.Bd -literal
const { createHook } = require('node:async_hooks');

createHook({ init() {} }).enable();
.Ed
.Bd -literal
$ node index.js
node:async_hooks:160
throw new ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED();
^

Error [ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED]: async_hooks.createHook() is disabled. Use --allow-async-hooks to enable it.
at createHook (node:async_hooks:160:11)
at Object.<anonymous> (/home/user/index.js:3:1)
...
.Ed
.Bd -literal
$ node --allow-async-hooks index.js
# Works without error
.Ed
.
.It Fl -allow-child-process
When using the Permission Model, the process will not be able to spawn any
child process by default.
Expand Down Expand Up @@ -2167,6 +2197,8 @@ one is included in the list below.
.It
\fB--allow-addons\fR
.It
\fB--allow-async-hooks\fR
.It
\fB--allow-child-process\fR
.It
\fB--allow-env\fR
Expand Down
122 changes: 8 additions & 114 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
'use strict';

const {
ArrayPrototypeIncludes,
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
FunctionPrototypeBind,
NumberIsSafeInteger,
Expand All @@ -15,38 +11,33 @@ const {
} = primordials;

const {
ERR_ASYNC_CALLBACK,
ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED,
ERR_ASYNC_RESOURCE_DOMAIN_REMOVED,
ERR_ASYNC_TYPE,
ERR_INVALID_ASYNC_ID,
ERR_INVALID_ARG_VALUE,
} = require('internal/errors').codes;
const {
kEmptyObject,
} = require('internal/util');
const {
validateBoolean,
validateFunction,
validateString,
} = require('internal/validators');
const { getOptionValue } = require('internal/options');
const internal_async_hooks = require('internal/async_hooks');

const AsyncContextFrame = require('internal/async_context_frame');

// Get functions
// For userland AsyncResources, make sure to emit a destroy event when the
// resource gets gced.
const { registerDestroyHook, kNoPromiseHook } = internal_async_hooks;
const { registerDestroyHook } = internal_async_hooks;
const {
asyncWrap,
executionAsyncId,
triggerAsyncId,
// Private API
hasAsyncIdStack,
getHookArrays,
enableHooks,
disableHooks,
updatePromiseHookMode,
executionAsyncResource,
// Internal Embedder API
newAsyncId,
Expand All @@ -57,118 +48,21 @@ const {
emitDestroy,
initHooksExist,
destroyHooksExist,
createHook: createHookInternal,
} = 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;

// Listener API //

class AsyncHook {
constructor({ init, before, after, destroy, promiseResolve, trackPromises }) {
if (init !== undefined && typeof init !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.init');
if (before !== undefined && typeof before !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.before');
if (after !== undefined && typeof after !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.after');
if (destroy !== undefined && typeof destroy !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.destroy');
if (promiseResolve !== undefined && typeof promiseResolve !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.promiseResolve');
if (trackPromises !== undefined) {
validateBoolean(trackPromises, 'trackPromises');
}

this[init_symbol] = init;
this[before_symbol] = before;
this[after_symbol] = after;
this[destroy_symbol] = destroy;
this[promise_resolve_symbol] = promiseResolve;
if (trackPromises === false) {
if (promiseResolve) {
throw new ERR_INVALID_ARG_VALUE('trackPromises',
trackPromises, 'must not be false when promiseResolve is enabled');
}
this[kNoPromiseHook] = true;
} else {
// Default to tracking promises for now.
this[kNoPromiseHook] = false;
}
}

enable() {
// The set of callbacks for a hook should be the same regardless of whether
// enable()/disable() are run during their execution. The following
// references are reassigned to the tmp arrays if a hook is currently being
// processed.
const { 0: hooks_array, 1: hook_fields } = getHookArrays();

// Each hook is only allowed to be added once.
if (ArrayPrototypeIncludes(hooks_array, this))
return this;

const prev_kTotals = hook_fields[kTotals];

// createHook() has already enforced that the callbacks are all functions,
// so here simply increment the count of whether each callbacks exists or
// not.
hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol];
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
hook_fields[kTotals] +=
hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
ArrayPrototypePush(hooks_array, this);

if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
enableHooks();
}

if (!this[kNoPromiseHook]) {
updatePromiseHookMode();
}

return this;
}

disable() {
const { 0: hooks_array, 1: hook_fields } = getHookArrays();

const index = ArrayPrototypeIndexOf(hooks_array, this);
if (index === -1)
return this;

const prev_kTotals = hook_fields[kTotals];

hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol];
hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
hook_fields[kTotals] +=
hook_fields[kPromiseResolve] -= +!!this[promise_resolve_symbol];
ArrayPrototypeSplice(hooks_array, index, 1);

if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
disableHooks();
}

return this;
}
}


function createHook(fns) {
return new AsyncHook(fns);
if (!getOptionValue('--allow-async-hooks')) {
throw new ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED();
}
return createHookInternal(fns);
}


Expand Down
Loading
Loading