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

child_process: add AbortSignal support #36308

Closed
Closed
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
17 changes: 17 additions & 0 deletions doc/api/child_process.md
Original file line number Diff line number Diff line change
@@ -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.
@@ -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}
@@ -330,6 +334,19 @@ 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
});
signal.abort();
```

### `child_process.fork(modulePath[, args][, options])`
<!-- YAML
added: v0.5.0
32 changes: 29 additions & 3 deletions lib/child_process.js
Original file line number Diff line number Diff line change
@@ -58,15 +58,24 @@ let debug = require('internal/util/debuglog').debuglog(
);
const { Buffer } = require('buffer');
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');

const {
AbortError,
codes: errorCodes,
} = require('internal/errors');
const {
ERR_INVALID_ARG_VALUE,
ERR_CHILD_PROCESS_IPC_REQUIRED,
ERR_CHILD_PROCESS_STDIO_MAXBUFFER,
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE
} = require('internal/errors').codes;
ERR_OUT_OF_RANGE,
} = errorCodes;
const { clearTimeout, setTimeout } = require('timers');
const { validateString, isInt32 } = require('internal/validators');
const {
validateString,
isInt32,
validateAbortSignal,
} = require('internal/validators');
const child_process = require('internal/child_process');
const {
getValidStdio,
@@ -245,6 +254,9 @@ function execFile(file /* , args, options, callback */) {
// Validate maxBuffer, if present.
validateMaxBuffer(options.maxBuffer);

// Validate signal, if present
validateAbortSignal(options.signal, 'options.signal');

options.killSignal = sanitizeKillSignal(options.killSignal);

const child = spawn(file, args, {
@@ -362,6 +374,20 @@ function execFile(file /* , args, options, callback */) {
timeoutId = null;
}, options.timeout);
}
if (options.signal) {
if (options.signal.aborted) {
process.nextTick(() => kill());
} else {
options.signal.addEventListener('abort', () => {
if (!ex) {
ex = new AbortError();
}
kill();
});
const remove = () => options.signal.removeEventListener('abort', kill);
child.once('close', remove);
}
}

if (child.stdout) {
if (encoding)
1 change: 1 addition & 0 deletions test/parallel/test-bootstrap-modules.js
Original file line number Diff line number Diff line change
@@ -114,6 +114,7 @@ if (!common.isMainThread) {
'Internal Binding performance',
'Internal Binding symbols',
'Internal Binding worker',
'NativeModule internal/streams/add-abort-signal',
'NativeModule internal/streams/duplex',
'NativeModule internal/streams/passthrough',
'NativeModule internal/streams/readable',
14 changes: 14 additions & 0 deletions test/parallel/test-child-process-execfile.js
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ const { getSystemErrorName } = require('util');
const fixtures = require('../common/fixtures');

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

{
@@ -45,3 +46,16 @@ const execOpts = { encoding: 'utf8', shell: true };
// Verify the shell option works properly
execFile(process.execPath, [fixture, 0], execOpts, common.mustSucceed());
}

{
// Verify that the signal option works properly
const ac = new AbortController();
const { signal } = ac;

const callback = common.mustCall((err) => {
assert.strictEqual(err.code, 'ABORT_ERR');
assert.strictEqual(err.name, 'AbortError');
});
execFile(process.execPath, [echoFixture, 0], { signal }, callback);
ac.abort();
}