Skip to content
Open
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.
}
```

### `--enable-async-hooks`

<!-- YAML
added: REPLACEME
-->

Enable 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 --enable-async-hooks to enable it.
at createHook (node:async_hooks:160:11)
at Object.<anonymous> (/home/user/index.js:3:1)
...
```

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

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

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

* `--allow-addons`
* `--enable-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
[`--enable-async-hooks`][] flag.

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

### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED`
Expand Down Expand Up @@ -4774,6 +4785,7 @@ An error occurred trying to allocate memory. This should never happen.
[`"imports"`]: packages.md#imports
[`'uncaughtException'`]: process.md#event-uncaughtexception
[`--disable-proto=throw`]: cli.md#--disable-protomode
[`--enable-async-hooks`]: cli.md#--enable-async-hooks
[`--force-fips`]: cli.md#--force-fips
[`--no-addons`]: cli.md#--no-addons
[`--unhandled-rejections`]: cli.md#--unhandled-rejectionsmode
Expand All @@ -4798,6 +4810,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 -enable-async-hooks
Enable 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 --enable-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 --enable-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 @@ -2189,6 +2219,8 @@ one is included in the list below.
.It
\fB--allow-addons\fR
.It
\fB--enable-async-hooks\fR
.It
\fB--allow-child-process\fR
.It
\fB--allow-env\fR
Expand Down
5 changes: 5 additions & 0 deletions lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ const {

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 { getOptionValue } = require('internal/options');
const {
kEmptyObject,
} = require('internal/util');
Expand Down Expand Up @@ -168,6 +170,9 @@ class AsyncHook {


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

Expand Down
2 changes: 2 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,8 @@ E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
E('ERR_ASSERTION', '%s', Error);
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
E('ERR_ASYNC_HOOKS_CREATE_HOOK_DISABLED',
'async_hooks.createHook() is disabled. Use --enable-async-hooks to enable it.', Error);
E('ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED',
'Async loader request never settled', Error);
E('ERR_ASYNC_RESOURCE_DOMAIN_REMOVED',
Expand Down
82 changes: 74 additions & 8 deletions lib/internal/inspector_async_hook.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
'use strict';

const {
ArrayPrototypeIncludes,
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSplice,
} = primordials;

const {
getHookArrays,
enableHooks,
disableHooks,
symbols: {
init_symbol,
before_symbol,
after_symbol,
destroy_symbol,
},
constants: {
kInit,
kBefore,
kAfter,
kDestroy,
kTotals,
},
kNoPromiseHook,
} = require('internal/async_hooks');

let hook;
let config;

function lazyHookCreation() {
const inspector = internalBinding('inspector');
const { createHook } = require('async_hooks');
config = internalBinding('config');

hook = createHook({
init(asyncId, type, triggerAsyncId, resource) {
hook = {
[init_symbol](asyncId, type, triggerAsyncId, resource) {
// It's difficult to tell which tasks will be recurring and which won't,
// therefore we mark all tasks as recurring. Based on the discussion
// in https://github.com/nodejs/node/pull/13870#discussion_r124515293,
Expand All @@ -18,19 +44,59 @@ function lazyHookCreation() {
inspector.asyncTaskScheduled(type, asyncId, recurring);
},

before(asyncId) {
[before_symbol](asyncId) {
inspector.asyncTaskStarted(asyncId);
},

after(asyncId) {
[after_symbol](asyncId) {
inspector.asyncTaskFinished(asyncId);
},

destroy(asyncId) {
[destroy_symbol](asyncId) {
inspector.asyncTaskCanceled(asyncId);
},
trackPromises: false,
});

// The inspector does not want promise hook callbacks.
[kNoPromiseHook]: true,

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

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

// This hook always registers all four callbacks, so each count is
// incremented exactly once.
const prev_kTotals = hook_fields[kTotals];
hook_fields[kTotals] = hook_fields[kInit] += 1;
hook_fields[kTotals] += hook_fields[kBefore] += 1;
hook_fields[kTotals] += hook_fields[kAfter] += 1;
hook_fields[kTotals] += hook_fields[kDestroy] += 1;
ArrayPrototypePush(hooks_array, this);

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

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

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

const prev_kTotals = hook_fields[kTotals];
hook_fields[kTotals] = hook_fields[kInit] -= 1;
hook_fields[kTotals] += hook_fields[kBefore] -= 1;
hook_fields[kTotals] += hook_fields[kAfter] -= 1;
hook_fields[kTotals] += hook_fields[kDestroy] -= 1;
ArrayPrototypeSplice(hooks_array, index, 1);

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

function enable() {
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--enable-async-hooks",
"enable use of async_hooks.createHook",
BOOL_FIELD(enable_async_hooks),
kAllowedInEnvvar);
AddOption("--allow-openssl-store",
"allow use of OpenSSL STORE loaders when any permissions are set",
BOOL_FIELD(allow_openssl_store),
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ class EnvironmentOptions : public Options {
DEFINE_BOOL_FIELD(allow_ffi) = false;
DEFINE_BOOL_FIELD(allow_openssl_store) = false;
DEFINE_BOOL_FIELD(allow_worker_threads) = false;
DEFINE_BOOL_FIELD(enable_async_hooks) = false;
DEFINE_BOOL_FIELD(experimental_vm_modules) = EXPERIMENTALS_DEFAULT_VALUE;
DEFINE_BOOL_FIELD(expose_internals) = false;
DEFINE_BOOL_FIELD(force_node_api_uncaught_exceptions_policy) = false;
Expand Down
1 change: 1 addition & 0 deletions test/addons/async-resource/test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks

const common = require('../../common');
const assert = require('assert');
Expand Down
1 change: 1 addition & 0 deletions test/addons/callback-scope/test-async-hooks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks

const common = require('../../common');
const assert = require('assert');
Expand Down
2 changes: 1 addition & 1 deletion test/async-hooks/init-hooks.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';
// Flags: --expose-gc
// Flags: --enable-async-hooks --expose-gc

require('../common');
const assert = require('assert');
Expand Down
1 change: 1 addition & 0 deletions test/async-hooks/test-async-await.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks
const common = require('../common');

// This test ensures async hooks are being properly called
Expand Down
1 change: 1 addition & 0 deletions test/async-hooks/test-async-exec-resource-http-32060.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks
const common = require('../common');
const assert = require('assert');
const {
Expand Down
1 change: 1 addition & 0 deletions test/async-hooks/test-async-exec-resource-http-agent.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks

const common = require('../common');

Expand Down
1 change: 1 addition & 0 deletions test/async-hooks/test-async-exec-resource-http.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks

const common = require('../common');
const assert = require('assert');
Expand Down
1 change: 1 addition & 0 deletions test/async-hooks/test-async-exec-resource-match.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks

const common = require('../common');
const assert = require('assert');
Expand Down
2 changes: 1 addition & 1 deletion test/async-hooks/test-async-local-storage-gcable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';
// Flags: --expose_gc
// Flags: --enable-async-hooks --expose_gc

// This test ensures that AsyncLocalStorage gets gced once it was disabled
// and no strong references remain in userland.
Expand Down
9 changes: 5 additions & 4 deletions test/async-hooks/test-callback-error.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks
const common = require('../common');
const assert = require('assert');
const { spawnSync } = require('child_process');
Expand Down Expand Up @@ -37,7 +38,7 @@ assert.ok(!arg);
// console.log should stay until this test's flakiness is solved
console.log('start case 1');
console.time('end case 1');
const child = spawnSync(process.execPath, [__filename, 'test_init_callback']);
const child = spawnSync(process.execPath, ['--enable-async-hooks', __filename, 'test_init_callback']);
assert.ifError(child.error);
const test_init_first_line = child.stderr.toString().split(/[\r\n]+/g)[0];
assert.strictEqual(test_init_first_line, 'Error: test_init_callback');
Expand All @@ -48,7 +49,7 @@ assert.ok(!arg);
{
console.log('start case 2');
console.time('end case 2');
const child = spawnSync(process.execPath, [__filename, 'test_callback']);
const child = spawnSync(process.execPath, ['--enable-async-hooks', __filename, 'test_callback']);
assert.ifError(child.error);
const test_callback_first_line = child.stderr.toString().split(/[\r\n]+/g)[0];
assert.strictEqual(test_callback_first_line, 'Error: test_callback');
Expand All @@ -61,10 +62,10 @@ assert.ok(!arg);
console.time('end case 3');
let program = process.execPath;
let args = [
'--abort-on-uncaught-exception', __filename, 'test_callback_abort' ];
'--enable-async-hooks', '--abort-on-uncaught-exception', __filename, 'test_callback_abort' ];
let options = {};
if (!common.isWindows) {
[program, options] = common.escapePOSIXShell`ulimit -c 0 && exec "${program}" ${args[0]} "${args[1]}" ${args[2]}`;
[program, options] = common.escapePOSIXShell`ulimit -c 0 && exec "${program}" ${args[0]} ${args[1]} "${args[2]}" ${args[3]}`;
args = [];
options.shell = true;
}
Expand Down
1 change: 1 addition & 0 deletions test/async-hooks/test-crypto-pbkdf2.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
// Flags: --enable-async-hooks

const common = require('../common');
if (!common.hasCrypto) {
Expand Down
Loading
Loading