Skip to content

Commit 224a647

Browse files
benjamingrdanielleadams
authored andcommitted
child_process: add AbortSignal support
PR-URL: #36308 Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent e798770 commit 224a647

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

doc/api/child_process.md

+17
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ lsExample();
250250
<!-- YAML
251251
added: v0.1.91
252252
changes:
253+
- version: REPLACEME
254+
pr-url: https://github.com/nodejs/node/pull/36308
255+
description: AbortSignal support was added.
253256
- version: v8.8.0
254257
pr-url: https://github.com/nodejs/node/pull/15380
255258
description: The `windowsHide` option is supported now.
@@ -277,6 +280,7 @@ changes:
277280
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
278281
shell can be specified as a string. See [Shell requirements][] and
279282
[Default Windows shell][]. **Default:** `false` (no shell).
283+
* `signal` {AbortSignal} allows aborting the execFile using an AbortSignal
280284
* `callback` {Function} Called with the output when process terminates.
281285
* `error` {Error}
282286
* `stdout` {string|Buffer}
@@ -330,6 +334,19 @@ getVersion();
330334
function. Any input containing shell metacharacters may be used to trigger
331335
arbitrary command execution.**
332336

337+
If the `signal` option is enabled, calling `.abort()` on the corresponding
338+
`AbortController` is similar to calling `.kill()` on the child process except
339+
the error passed to the callback will be an `AbortError`:
340+
341+
```js
342+
const controller = new AbortController();
343+
const { signal } = controller;
344+
const child = execFile('node', ['--version'], { signal }, (error) => {
345+
console.log(error); // an AbortError
346+
});
347+
signal.abort();
348+
```
349+
333350
### `child_process.fork(modulePath[, args][, options])`
334351
<!-- YAML
335352
added: v0.5.0

lib/child_process.js

+29-3
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,24 @@ let debug = require('internal/util/debuglog').debuglog(
5858
);
5959
const { Buffer } = require('buffer');
6060
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');
61+
62+
const {
63+
AbortError,
64+
codes: errorCodes,
65+
} = require('internal/errors');
6166
const {
6267
ERR_INVALID_ARG_VALUE,
6368
ERR_CHILD_PROCESS_IPC_REQUIRED,
6469
ERR_CHILD_PROCESS_STDIO_MAXBUFFER,
6570
ERR_INVALID_ARG_TYPE,
66-
ERR_OUT_OF_RANGE
67-
} = require('internal/errors').codes;
71+
ERR_OUT_OF_RANGE,
72+
} = errorCodes;
6873
const { clearTimeout, setTimeout } = require('timers');
69-
const { validateString, isInt32 } = require('internal/validators');
74+
const {
75+
validateString,
76+
isInt32,
77+
validateAbortSignal,
78+
} = require('internal/validators');
7079
const child_process = require('internal/child_process');
7180
const {
7281
getValidStdio,
@@ -245,6 +254,9 @@ function execFile(file /* , args, options, callback */) {
245254
// Validate maxBuffer, if present.
246255
validateMaxBuffer(options.maxBuffer);
247256

257+
// Validate signal, if present
258+
validateAbortSignal(options.signal, 'options.signal');
259+
248260
options.killSignal = sanitizeKillSignal(options.killSignal);
249261

250262
const child = spawn(file, args, {
@@ -362,6 +374,20 @@ function execFile(file /* , args, options, callback */) {
362374
timeoutId = null;
363375
}, options.timeout);
364376
}
377+
if (options.signal) {
378+
if (options.signal.aborted) {
379+
process.nextTick(() => kill());
380+
} else {
381+
options.signal.addEventListener('abort', () => {
382+
if (!ex) {
383+
ex = new AbortError();
384+
}
385+
kill();
386+
});
387+
const remove = () => options.signal.removeEventListener('abort', kill);
388+
child.once('close', remove);
389+
}
390+
}
365391

366392
if (child.stdout) {
367393
if (encoding)

test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ if (!common.isMainThread) {
114114
'Internal Binding performance',
115115
'Internal Binding symbols',
116116
'Internal Binding worker',
117+
'NativeModule internal/streams/add-abort-signal',
117118
'NativeModule internal/streams/duplex',
118119
'NativeModule internal/streams/passthrough',
119120
'NativeModule internal/streams/readable',

test/parallel/test-child-process-execfile.js

+14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { getSystemErrorName } = require('util');
77
const fixtures = require('../common/fixtures');
88

99
const fixture = fixtures.path('exit.js');
10+
const echoFixture = fixtures.path('echo.js');
1011
const execOpts = { encoding: 'utf8', shell: true };
1112

1213
{
@@ -45,3 +46,16 @@ const execOpts = { encoding: 'utf8', shell: true };
4546
// Verify the shell option works properly
4647
execFile(process.execPath, [fixture, 0], execOpts, common.mustSucceed());
4748
}
49+
50+
{
51+
// Verify that the signal option works properly
52+
const ac = new AbortController();
53+
const { signal } = ac;
54+
55+
const callback = common.mustCall((err) => {
56+
assert.strictEqual(err.code, 'ABORT_ERR');
57+
assert.strictEqual(err.name, 'AbortError');
58+
});
59+
execFile(process.execPath, [echoFixture, 0], { signal }, callback);
60+
ac.abort();
61+
}

0 commit comments

Comments
 (0)