Skip to content

Commit 0b691ce

Browse files
benjamingrtargos
authored andcommitted
child_process: add signal support to spawn
PR-URL: #36432 Backport-PR-URL: #38386 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 6c08c9d commit 0b691ce

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-3
lines changed

doc/api/child_process.md

+21-2
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ changes:
280280
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
281281
shell can be specified as a string. See [Shell requirements][] and
282282
[Default Windows shell][]. **Default:** `false` (no shell).
283-
* `signal` {AbortSignal} allows aborting the execFile using an AbortSignal
283+
* `signal` {AbortSignal} allows aborting the execFile using an AbortSignal.
284284
* `callback` {Function} Called with the output when process terminates.
285285
* `error` {Error}
286286
* `stdout` {string|Buffer}
@@ -344,7 +344,7 @@ const { signal } = controller;
344344
const child = execFile('node', ['--version'], { signal }, (error) => {
345345
console.log(error); // an AbortError
346346
});
347-
signal.abort();
347+
controller.abort();
348348
```
349349

350350
### `child_process.fork(modulePath[, args][, options])`
@@ -424,6 +424,9 @@ The `shell` option available in [`child_process.spawn()`][] is not supported by
424424
<!-- YAML
425425
added: v0.1.90
426426
changes:
427+
- version: REPLACEME
428+
pr-url: https://github.com/nodejs/node/pull/36432
429+
description: AbortSignal support was added.
427430
- version:
428431
- v13.2.0
429432
- v12.16.0
@@ -466,6 +469,8 @@ changes:
466469
when `shell` is specified and is CMD. **Default:** `false`.
467470
* `windowsHide` {boolean} Hide the subprocess console window that would
468471
normally be created on Windows systems. **Default:** `false`.
472+
* `signal` {AbortSignal} allows aborting the execFile using an AbortSignal.
473+
469474
* Returns: {ChildProcess}
470475

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

580+
If the `signal` option is enabled, calling `.abort()` on the corresponding
581+
`AbortController` is similar to calling `.kill()` on the child process except
582+
the error passed to the callback will be an `AbortError`:
583+
584+
```js
585+
const controller = new AbortController();
586+
const { signal } = controller;
587+
const grep = spawn('grep', ['ssh'], { signal });
588+
grep.on('error', (err) => {
589+
// This will be called with err being an AbortError if the controller aborts
590+
});
591+
controller.abort(); // stops the process
592+
```
593+
575594
#### `options.detached`
576595
<!-- YAML
577596
added: v0.7.10

lib/child_process.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,30 @@ function sanitizeKillSignal(killSignal) {
709709
}
710710
}
711711

712+
// This level of indirection is here because the other child_process methods
713+
// call spawn internally but should use different cancellation logic.
714+
function spawnWithSignal(file, args, options) {
715+
const child = spawn(file, args, options);
716+
717+
if (options && options.signal) {
718+
// Validate signal, if present
719+
validateAbortSignal(options.signal, 'options.signal');
720+
function kill() {
721+
if (child._handle) {
722+
child._handle.kill('SIGTERM');
723+
child.emit('error', new AbortError());
724+
}
725+
}
726+
if (options.signal.aborted) {
727+
process.nextTick(kill);
728+
} else {
729+
options.signal.addEventListener('abort', kill);
730+
const remove = () => options.signal.removeEventListener('abort', kill);
731+
child.once('close', remove);
732+
}
733+
}
734+
return child;
735+
}
712736
module.exports = {
713737
_forkChild,
714738
ChildProcess,
@@ -717,6 +741,6 @@ module.exports = {
717741
execFileSync,
718742
execSync,
719743
fork,
720-
spawn,
744+
spawn: spawnWithSignal,
721745
spawnSync
722746
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Flags: --experimental-abortcontroller
2+
'use strict';
3+
4+
const common = require('../common');
5+
const assert = require('assert');
6+
const cp = require('child_process');
7+
8+
// Verify that passing an AbortSignal works
9+
const controller = new AbortController();
10+
const { signal } = controller;
11+
12+
const echo = cp.spawn('echo', ['fun'], {
13+
encoding: 'utf8',
14+
shell: true,
15+
signal
16+
});
17+
18+
echo.on('error', common.mustCall((e) => {
19+
assert.strictEqual(e.name, 'AbortError');
20+
}));
21+
22+
controller.abort();

0 commit comments

Comments
 (0)