Skip to content

Commit 5d91c27

Browse files
committed
async_hooks: add --allow-async-hooks flag
Add a new `--allow-async-hooks` flag that must be passed to enable `async_hooks.createHook()`. This is a breaking change - createHook is now disabled by default. When createHook() is called without the flag, it throws ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED with a message indicating that the flag is required. This change affects all code using async_hooks.createHook(), including APM tools and debugging utilities. Users must now explicitly opt-in to this functionality. BREAKING CHANGE: async_hooks.createHook() now requires --allow-async-hooks Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 77609be commit 5d91c27

190 files changed

Lines changed: 454 additions & 194 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎doc/api/cli.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,46 @@ Error: Cannot load native addon because loading addons is disabled.
139139
}
140140
```
141141

142+
### `--allow-async-hooks`
143+
144+
<!-- YAML
145+
added: REPLACEME
146+
-->
147+
148+
Allow use of `async_hooks.createHook()`. This flag is required to enable
149+
the `createHook` API, which is disabled by default.
150+
151+
Example:
152+
153+
```mjs
154+
import { createHook } from 'node:async_hooks';
155+
156+
createHook({ init() {} }).enable();
157+
```
158+
159+
```cjs
160+
const { createHook } = require('node:async_hooks');
161+
162+
createHook({ init() {} }).enable();
163+
```
164+
165+
```console
166+
$ node index.js
167+
node:async_hooks:160
168+
throw new ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED();
169+
^
170+
171+
Error [ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED]: async_hooks.createHook() is disabled. Use --allow-async-hooks to enable it.
172+
at createHook (node:async_hooks:160:11)
173+
at Object.<anonymous> (/home/user/index.js:3:1)
174+
...
175+
```
176+
177+
```console
178+
$ node --allow-async-hooks index.js
179+
# Works without error
180+
```
181+
142182
### `--allow-child-process`
143183

144184
<!-- YAML
@@ -4233,6 +4273,7 @@ one is included in the list below.
42334273
<!-- node-options-node start -->
42344274

42354275
* `--allow-addons`
4276+
* `--allow-async-hooks`
42364277
* `--allow-child-process`
42374278
* `--allow-env`
42384279
* `--allow-ffi`

‎doc/api/errors.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,17 @@ by the `node:assert` module.
733733
An attempt was made to register something that is not a function as an
734734
`AsyncHooks` callback.
735735

736+
<a id="ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED"></a>
737+
738+
### `ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED`
739+
740+
<!-- YAML
741+
added: REPLACEME
742+
-->
743+
744+
An attempt was made to call [`async_hooks.createHook()`][] without the
745+
[`--allow-async-hooks`][] flag.
746+
736747
<a id="ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED"></a>
737748

738749
### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED`
@@ -4770,6 +4781,7 @@ An error occurred trying to allocate memory. This should never happen.
47704781
[`"exports"`]: packages.md#exports
47714782
[`"imports"`]: packages.md#imports
47724783
[`'uncaughtException'`]: process.md#event-uncaughtexception
4784+
[`--allow-async-hooks`]: cli.md#--allow-async-hooks
47734785
[`--disable-proto=throw`]: cli.md#--disable-protomode
47744786
[`--force-fips`]: cli.md#--force-fips
47754787
[`--no-addons`]: cli.md#--no-addons
@@ -4795,6 +4807,7 @@ An error occurred trying to allocate memory. This should never happen.
47954807
[`Writable`]: stream.md#class-streamwritable
47964808
[`ZipBuffer`]: zlib.md#class-zlibzipbuffer
47974809
[`ZipFile`]: zlib.md#class-zlibzipfile
4810+
[`async_hooks.createHook()`]: async_hooks.md#async_hookscreatehookoptions
47984811
[`child_process`]: child_process.md
47994812
[`cipher.getAuthTag()`]: crypto.md#ciphergetauthtag
48004813
[`crypto.getDiffieHellman()`]: crypto.md#cryptogetdiffiehellmangroupname

‎doc/node.1‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,36 @@ Error: Cannot load native addon because loading addons is disabled.
8686
}
8787
.Ed
8888
.
89+
.It Fl -allow-async-hooks
90+
Allow use of \fBasync_hooks.createHook()\fR. This flag is required to enable
91+
the \fBcreateHook\fR API, which is disabled by default.
92+
Example:
93+
.Bd -literal
94+
import { createHook } from 'node:async_hooks';
95+
96+
createHook({ init() {} }).enable();
97+
.Ed
98+
.Bd -literal
99+
const { createHook } = require('node:async_hooks');
100+
101+
createHook({ init() {} }).enable();
102+
.Ed
103+
.Bd -literal
104+
$ node index.js
105+
node:async_hooks:160
106+
throw new ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED();
107+
^
108+
109+
Error [ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED]: async_hooks.createHook() is disabled. Use --allow-async-hooks to enable it.
110+
at createHook (node:async_hooks:160:11)
111+
at Object.<anonymous> (/home/user/index.js:3:1)
112+
...
113+
.Ed
114+
.Bd -literal
115+
$ node --allow-async-hooks index.js
116+
# Works without error
117+
.Ed
118+
.
89119
.It Fl -allow-child-process
90120
When using the Permission Model, the process will not be able to spawn any
91121
child process by default.
@@ -2167,6 +2197,8 @@ one is included in the list below.
21672197
.It
21682198
\fB--allow-addons\fR
21692199
.It
2200+
\fB--allow-async-hooks\fR
2201+
.It
21702202
\fB--allow-child-process\fR
21712203
.It
21722204
\fB--allow-env\fR

‎lib/async_hooks.js‎

Lines changed: 8 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
'use strict';
22

33
const {
4-
ArrayPrototypeIncludes,
5-
ArrayPrototypeIndexOf,
6-
ArrayPrototypePush,
7-
ArrayPrototypeSplice,
84
ArrayPrototypeUnshift,
95
FunctionPrototypeBind,
106
NumberIsSafeInteger,
@@ -15,38 +11,33 @@ const {
1511
} = primordials;
1612

1713
const {
18-
ERR_ASYNC_CALLBACK,
14+
ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED,
1915
ERR_ASYNC_RESOURCE_DOMAIN_REMOVED,
2016
ERR_ASYNC_TYPE,
2117
ERR_INVALID_ASYNC_ID,
22-
ERR_INVALID_ARG_VALUE,
2318
} = require('internal/errors').codes;
2419
const {
2520
kEmptyObject,
2621
} = require('internal/util');
2722
const {
28-
validateBoolean,
2923
validateFunction,
3024
validateString,
3125
} = require('internal/validators');
26+
const { getOptionValue } = require('internal/options');
3227
const internal_async_hooks = require('internal/async_hooks');
3328

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

3631
// Get functions
3732
// For userland AsyncResources, make sure to emit a destroy event when the
3833
// resource gets gced.
39-
const { registerDestroyHook, kNoPromiseHook } = internal_async_hooks;
34+
const { registerDestroyHook } = internal_async_hooks;
4035
const {
4136
asyncWrap,
4237
executionAsyncId,
4338
triggerAsyncId,
4439
// Private API
4540
hasAsyncIdStack,
46-
getHookArrays,
47-
enableHooks,
48-
disableHooks,
49-
updatePromiseHookMode,
5041
executionAsyncResource,
5142
// Internal Embedder API
5243
newAsyncId,
@@ -57,118 +48,21 @@ const {
5748
emitDestroy,
5849
initHooksExist,
5950
destroyHooksExist,
51+
createHook: createHookInternal,
6052
} = internal_async_hooks;
6153

6254
// Get symbols
6355
const {
6456
async_id_symbol, trigger_async_id_symbol,
65-
init_symbol, before_symbol, after_symbol, destroy_symbol,
66-
promise_resolve_symbol,
6757
} = internal_async_hooks.symbols;
6858

69-
// Get constants
70-
const {
71-
kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
72-
} = internal_async_hooks.constants;
73-
7459
// Listener API //
7560

76-
class AsyncHook {
77-
constructor({ init, before, after, destroy, promiseResolve, trackPromises }) {
78-
if (init !== undefined && typeof init !== 'function')
79-
throw new ERR_ASYNC_CALLBACK('hook.init');
80-
if (before !== undefined && typeof before !== 'function')
81-
throw new ERR_ASYNC_CALLBACK('hook.before');
82-
if (after !== undefined && typeof after !== 'function')
83-
throw new ERR_ASYNC_CALLBACK('hook.after');
84-
if (destroy !== undefined && typeof destroy !== 'function')
85-
throw new ERR_ASYNC_CALLBACK('hook.destroy');
86-
if (promiseResolve !== undefined && typeof promiseResolve !== 'function')
87-
throw new ERR_ASYNC_CALLBACK('hook.promiseResolve');
88-
if (trackPromises !== undefined) {
89-
validateBoolean(trackPromises, 'trackPromises');
90-
}
91-
92-
this[init_symbol] = init;
93-
this[before_symbol] = before;
94-
this[after_symbol] = after;
95-
this[destroy_symbol] = destroy;
96-
this[promise_resolve_symbol] = promiseResolve;
97-
if (trackPromises === false) {
98-
if (promiseResolve) {
99-
throw new ERR_INVALID_ARG_VALUE('trackPromises',
100-
trackPromises, 'must not be false when promiseResolve is enabled');
101-
}
102-
this[kNoPromiseHook] = true;
103-
} else {
104-
// Default to tracking promises for now.
105-
this[kNoPromiseHook] = false;
106-
}
107-
}
108-
109-
enable() {
110-
// The set of callbacks for a hook should be the same regardless of whether
111-
// enable()/disable() are run during their execution. The following
112-
// references are reassigned to the tmp arrays if a hook is currently being
113-
// processed.
114-
const { 0: hooks_array, 1: hook_fields } = getHookArrays();
115-
116-
// Each hook is only allowed to be added once.
117-
if (ArrayPrototypeIncludes(hooks_array, this))
118-
return this;
119-
120-
const prev_kTotals = hook_fields[kTotals];
121-
122-
// createHook() has already enforced that the callbacks are all functions,
123-
// so here simply increment the count of whether each callbacks exists or
124-
// not.
125-
hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol];
126-
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
127-
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
128-
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
129-
hook_fields[kTotals] +=
130-
hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
131-
ArrayPrototypePush(hooks_array, this);
132-
133-
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
134-
enableHooks();
135-
}
136-
137-
if (!this[kNoPromiseHook]) {
138-
updatePromiseHookMode();
139-
}
140-
141-
return this;
142-
}
143-
144-
disable() {
145-
const { 0: hooks_array, 1: hook_fields } = getHookArrays();
146-
147-
const index = ArrayPrototypeIndexOf(hooks_array, this);
148-
if (index === -1)
149-
return this;
150-
151-
const prev_kTotals = hook_fields[kTotals];
152-
153-
hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol];
154-
hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
155-
hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
156-
hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
157-
hook_fields[kTotals] +=
158-
hook_fields[kPromiseResolve] -= +!!this[promise_resolve_symbol];
159-
ArrayPrototypeSplice(hooks_array, index, 1);
160-
161-
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
162-
disableHooks();
163-
}
164-
165-
return this;
166-
}
167-
}
168-
169-
17061
function createHook(fns) {
171-
return new AsyncHook(fns);
62+
if (!getOptionValue('--allow-async-hooks')) {
63+
throw new ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED();
64+
}
65+
return createHookInternal(fns);
17266
}
17367

17468

0 commit comments

Comments
 (0)