Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v14.x] Backport AbortController and friends #38386

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1cff8fd
lib: initial experimental AbortController implementation
jasnell May 23, 2020
c86e131
timers: allow promisified timeouts/immediates to be canceled
jasnell Jun 10, 2020
290ce9f
timers: fix multipleResolves in promisified timeouts/immediates
lundibundi Jun 18, 2020
d097171
timers: move promisified timers implementations
jasnell Jun 18, 2020
26b7626
timers: use AbortController with correct name/message
addaleax Aug 13, 2020
268686a
events: allow use of AbortController with once
jasnell Aug 24, 2020
51031f3
events: allow use of AbortController with on
jasnell Aug 24, 2020
affb52d
doc: revise AbortSignal text and example using events.once()
Trott Sep 1, 2020
561d832
doc: make AbortSignal text consistent in events.md
Trott Sep 3, 2020
b389182
events: make abort_controller event trusted
benjamingr Oct 26, 2020
f76ab5a
lib: let abort_controller target be EventTarget
watilde Oct 30, 2020
76cdf87
test: integrate abort_controller tests from wpt
watilde Oct 30, 2020
dc3d852
fs: add support for AbortSignal in readFile
benjamingr Nov 1, 2020
ceef7e3
fs: support abortsignal in writeFile
benjamingr Nov 6, 2020
01d2b67
events: add a few tests
benjamingr Oct 26, 2020
1ca549d
events: support emit on nodeeventtarget
benjamingr Oct 28, 2020
307bebc
events: define event handler as enumerable
benjamingr Nov 2, 2020
902503c
events: support event handlers on prototypes
benjamingr Nov 2, 2020
6bbd82f
events: define abort on prototype
benjamingr Nov 2, 2020
7d255e6
events: fire handlers in correct oder
benjamingr Nov 6, 2020
0131586
events: disabled manual construction AbortSignal
RaisinTen Nov 12, 2020
555a5a2
events: getEventListeners static
benjamingr Nov 6, 2020
e8bd59c
http: add support for abortsignal to http.request
benjamingr Nov 9, 2020
5ce6f91
http2: add support for AbortSignal to http2Session.request
MadaraUchiha Nov 10, 2020
8277ec1
child_process: add AbortSignal support
benjamingr Nov 28, 2020
6c86e68
test: increase execFile abort coverage
shootermv Dec 7, 2020
4f04104
child_process: clean event listener correctly
benjamingr Dec 7, 2020
44a304d
child_process: add signal support to spawn
benjamingr Dec 7, 2020
b9732d4
child_process: support AbortSignal in fork
benjamingr Dec 22, 2020
130b797
timers: refactor to use validateAbortSignal
Lxxyx Dec 22, 2020
1b04e63
dgram: support AbortSignal in createSocket
Jan 22, 2021
ef6af8c
fs: add AbortSignal support to watch
benjamingr Feb 2, 2021
88b3463
fs: fix pre-aborted writeFile AbortSignal file leak
Feb 16, 2021
a80d272
doc: recommend checking abortSignal.aborted first
jasnell Mar 11, 2021
6b3a21c
events: add max listener warning for EventTarget
jasnell Nov 6, 2020
a2cf563
lib: set abort-controller toStringTag
benjamingr Nov 14, 2020
436a2d2
lib: implement AbortSignal.abort()
jasnell Mar 10, 2021
63099e1
test: update dom/abort tests
jasnell Mar 15, 2021
10131cd
lib: add brand checks to AbortController and AbortSignal
MattiasBuelens Mar 11, 2021
9abc11a
test: fix unreliable test-fs-write-file.js
Trott Nov 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.js
Expand Up @@ -282,6 +282,8 @@ module.exports = {
'node-core/no-duplicate-requires': 'error',
},
globals: {
AbortController: 'readable',
AbortSignal: 'readable',
Atomics: 'readable',
BigInt: 'readable',
BigInt64Array: 'readable',
Expand Down
47 changes: 45 additions & 2 deletions doc/api/child_process.md
Expand Up @@ -250,6 +250,9 @@ lsExample();
<!-- YAML
added: v0.1.91
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/36308
description: AbortSignal support was added.
- version: v8.8.0
pr-url: https://github.com/nodejs/node/pull/15380
description: The `windowsHide` option is supported now.
Expand Down Expand Up @@ -277,6 +280,7 @@ changes:
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. See [Shell requirements][] and
[Default Windows shell][]. **Default:** `false` (no shell).
* `signal` {AbortSignal} allows aborting the execFile using an AbortSignal.
* `callback` {Function} Called with the output when process terminates.
* `error` {Error}
* `stdout` {string|Buffer}
Expand Down Expand Up @@ -330,10 +334,26 @@ getVersion();
function. Any input containing shell metacharacters may be used to trigger
arbitrary command execution.**

If the `signal` option is enabled, calling `.abort()` on the corresponding
`AbortController` is similar to calling `.kill()` on the child process except
the error passed to the callback will be an `AbortError`:

```js
const controller = new AbortController();
const { signal } = controller;
const child = execFile('node', ['--version'], { signal }, (error) => {
console.log(error); // an AbortError
});
controller.abort();
```

### `child_process.fork(modulePath[, args][, options])`
<!-- YAML
added: v0.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/36603
description: AbortSignal support was added.
- version:
- v13.2.0
- v12.16.0
Expand All @@ -358,9 +378,11 @@ changes:
* `execPath` {string} Executable used to create the child process.
* `execArgv` {string[]} List of string arguments passed to the executable.
**Default:** `process.execArgv`.
* `gid` {number} Sets the group identity of the process (see setgid(2)).
* `serialization` {string} Specify the kind of serialization used for sending
messages between processes. Possible values are `'json'` and `'advanced'`.
See [Advanced serialization][] for more details. **Default:** `'json'`.
* `signal` {AbortSignal} Allows closing the subprocess using an AbortSignal.
* `silent` {boolean} If `true`, stdin, stdout, and stderr of the child will be
piped to the parent, otherwise they will be inherited from the parent, see
the `'pipe'` and `'inherit'` options for [`child_process.spawn()`][]'s
Expand All @@ -369,10 +391,9 @@ changes:
When this option is provided, it overrides `silent`. If the array variant
is used, it must contain exactly one item with value `'ipc'` or an error
will be thrown. For instance `[0, 1, 2, 'ipc']`.
* `uid` {number} Sets the user identity of the process (see setuid(2)).
* `windowsVerbatimArguments` {boolean} No quoting or escaping of arguments is
done on Windows. Ignored on Unix. **Default:** `false`.
* `uid` {number} Sets the user identity of the process (see setuid(2)).
* `gid` {number} Sets the group identity of the process (see setgid(2)).
* Returns: {ChildProcess}

The `child_process.fork()` method is a special case of
Expand Down Expand Up @@ -403,10 +424,16 @@ current process.
The `shell` option available in [`child_process.spawn()`][] is not supported by
`child_process.fork()` and will be ignored if set.

The `signal` option works exactly the same way it does in
[`child_process.spawn()`][].

### `child_process.spawn(command[, args][, options])`
<!-- YAML
added: v0.1.90
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/36432
description: AbortSignal support was added.
- version:
- v13.2.0
- v12.16.0
Expand Down Expand Up @@ -449,6 +476,8 @@ changes:
when `shell` is specified and is CMD. **Default:** `false`.
* `windowsHide` {boolean} Hide the subprocess console window that would
normally be created on Windows systems. **Default:** `false`.
* `signal` {AbortSignal} allows aborting the execFile using an AbortSignal.

* Returns: {ChildProcess}

The `child_process.spawn()` method spawns a new process using the given
Expand Down Expand Up @@ -555,6 +584,20 @@ Node.js currently overwrites `argv[0]` with `process.execPath` on startup, so
parameter passed to `spawn` from the parent, retrieve it with the
`process.argv0` property instead.

If the `signal` option is enabled, calling `.abort()` on the corresponding
`AbortController` is similar to calling `.kill()` on the child process except
the error passed to the callback will be an `AbortError`:

```js
const controller = new AbortController();
const { signal } = controller;
const grep = spawn('grep', ['ssh'], { signal });
grep.on('error', (err) => {
// This will be called with err being an AbortError if the controller aborts
});
controller.abort(); // stops the process
```

#### `options.detached`
<!-- YAML
added: v0.7.10
Expand Down
8 changes: 8 additions & 0 deletions doc/api/cli.md
Expand Up @@ -197,6 +197,13 @@ Enable experimental Source Map v3 support for stack traces.
Currently, overriding `Error.prepareStackTrace` is ignored when the
`--enable-source-maps` flag is set.

### `--experimental-abortcontroller`
<!-- YAML
added: REPLACEME
-->

Enable experimental `AbortController` and `AbortSignal` support.

### `--experimental-import-meta-resolve`
<!-- YAML
added:
Expand Down Expand Up @@ -1222,6 +1229,7 @@ Node.js options that are allowed are:
* `--disable-proto`
* `--enable-fips`
* `--enable-source-maps`
* `--experimental-abortcontroller`
* `--experimental-import-meta-resolve`
* `--experimental-json-modules`
* `--experimental-loader`
Expand Down
18 changes: 18 additions & 0 deletions doc/api/dgram.md
Expand Up @@ -733,6 +733,9 @@ chained.
<!-- YAML
added: v0.11.13
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37026
description: AbortSignal support was added.
- version: v11.4.0
pr-url: https://github.com/nodejs/node/pull/23798
description: The `ipv6Only` option is supported.
Expand All @@ -757,6 +760,7 @@ changes:
* `recvBufferSize` {number} Sets the `SO_RCVBUF` socket value.
* `sendBufferSize` {number} Sets the `SO_SNDBUF` socket value.
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
* `signal` {AbortSignal} An AbortSignal that may be used to close a socket.
* `callback` {Function} Attached as a listener for `'message'` events. Optional.
* Returns: {dgram.Socket}

Expand All @@ -768,6 +772,20 @@ method will bind the socket to the "all interfaces" address on a random port
and port can be retrieved using [`socket.address().address`][] and
[`socket.address().port`][].

If the `signal` option is enabled, calling `.abort()` on the corresponding
`AbortController` is similar to calling `.close()` on the socket:

```js
const controller = new AbortController();
const { signal } = controller;
const server = dgram.createSocket({ type: 'udp4', signal });
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
// Later, when you want to close the server.
controller.abort();
```

### `dgram.createSocket(type[, callback])`
<!-- YAML
added: v0.1.99
Expand Down
123 changes: 119 additions & 4 deletions doc/api/events.md
Expand Up @@ -383,6 +383,29 @@ Installing a listener using this symbol does not change the behavior once an
`'error'` event is emitted, therefore the process will still crash if no
regular `'error'` listener is installed.

### `EventEmitter.setMaxListeners(n[, ...eventTargets])`
<!-- YAML
added: REPLACEME
-->

* `n` {number} A non-negative number. The maximum number of listeners per
`EventTarget` event.
* `...eventsTargets` {EventTarget[]|EventEmitter[]} Zero or more {EventTarget}
or {EventEmitter} instances. If none are specified, `n` is set as the default
max for all newly created {EventTarget} and {EventEmitter} objects.

```js
const {
setMaxListeners,
EventEmitter
} = require('events');

const target = new EventTarget();
const emitter = new EventEmitter();

setMaxListeners(5, target, emitter);
```

### `emitter.addListener(eventName, listener)`
<!-- YAML
added: v0.1.26
Expand Down Expand Up @@ -829,7 +852,41 @@ class MyClass extends EventEmitter {
}
```

## `events.once(emitter, name)`
## `events.getEventListeners(emitterOrTarget, eventName)`
<!-- YAML
added:
- REPLACEME
-->
* `emitterOrTarget` {EventEmitter|EventTarget}
* `eventName` {string|symbol}
* Returns: {Function[]}

Returns a copy of the array of listeners for the event named `eventName`.

For `EventEmitter`s this behaves exactly the same as calling `.listeners` on
the emitter.

For `EventTarget`s this is the only way to get the event listeners for the
event target. This is useful for debugging and diagnostic purposes.

```js
const { getEventListeners, EventEmitter } = require('events');

{
const ee = new EventEmitter();
const listener = () => console.log('Events are fun');
ee.on('foo', listener);
getEventListeners(ee, 'foo'); // [listener]
}
{
const et = new EventTarget();
const listener = () => console.log('Events are fun');
ee.addEventListener('foo', listener);
getEventListeners(ee, 'foo'); // [listener]
}
```

## `events.once(emitter, name[, options])`
<!-- YAML
added:
- v11.13.0
Expand All @@ -838,6 +895,8 @@ added:

* `emitter` {EventEmitter}
* `name` {string}
* `options` {Object}
* `signal` {AbortSignal} Can be used to cancel waiting for the event.
* Returns: {Promise}

Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given
Expand Down Expand Up @@ -896,6 +955,32 @@ ee.emit('error', new Error('boom'));
// Prints: ok boom
```

An {AbortSignal} can be used to cancel waiting for the event:

```js
const { EventEmitter, once } = require('events');

const ee = new EventEmitter();
const ac = new AbortController();

async function foo(emitter, event, signal) {
try {
await once(emitter, event, { signal });
console.log('event emitted!');
} catch (error) {
if (error.name === 'AbortError') {
console.error('Waiting for the event was canceled!');
} else {
console.error('There was an error', error.message);
}
}
}

foo(ee, 'foo', ac.signal);
ac.abort(); // Abort waiting for the event
ee.emit('foo'); // Prints: Waiting for the event was canceled!
```

### Awaiting multiple events emitted on `process.nextTick()`

There is an edge case worth noting when using the `events.once()` function
Expand Down Expand Up @@ -976,7 +1061,7 @@ Value: `Symbol.for('nodejs.rejection')`

See how to write a custom [rejection handler][rejection].

## `events.on(emitter, eventName)`
## `events.on(emitter, eventName[, options])`
<!-- YAML
added:
- v13.6.0
Expand All @@ -985,6 +1070,8 @@ added:

* `emitter` {EventEmitter}
* `eventName` {string|symbol} The name of the event being listened for
* `options` {Object}
* `signal` {AbortSignal} Can be used to cancel awaiting events.
* Returns: {AsyncIterator} that iterates `eventName` events emitted by the `emitter`

```js
Expand Down Expand Up @@ -1014,6 +1101,33 @@ if the `EventEmitter` emits `'error'`. It removes all listeners when
exiting the loop. The `value` returned by each iteration is an array
composed of the emitted event arguments.

An {AbortSignal} can be used to cancel waiting on events:

```js
const { on, EventEmitter } = require('events');
const ac = new AbortController();

(async () => {
const ee = new EventEmitter();

// Emit later on
process.nextTick(() => {
ee.emit('foo', 'bar');
ee.emit('foo', 42);
});

for await (const event of on(ee, 'foo', { signal: ac.signal })) {
// The execution of this inner block is synchronous and it
// processes one event at a time (even with await). Do not use
// if concurrent execution is required.
console.log(event); // prints ['bar'] [42]
}
// Unreachable here
})();

process.nextTick(() => ac.abort());
```

## `EventTarget` and `Event` API
<!-- YAML
added: v14.5.0
Expand Down Expand Up @@ -1215,9 +1329,10 @@ This is not used in Node.js and is provided purely for completeness.
added: v14.5.0
-->

* Type: {boolean} Always returns `false`.
* Type: {boolean} True for Node.js internal events, false otherwise.

This is not used in Node.js and is provided purely for completeness.
Currently only `AbortSignal`s' `"abort"` event is fired with `isTrusted`
set to `true`.

#### `event.preventDefault()`
<!-- YAML
Expand Down