Skip to content

Commit e449571

Browse files
benjamingrtargos
authored andcommitted
child_process: add signal support to spawn
PR-URL: #36432 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 62bfe3d commit e449571

File tree

3 files changed

+67
-3
lines changed

3 files changed

+67
-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
@@ -749,6 +749,30 @@ function sanitizeKillSignal(killSignal) {
749749
}
750750
}
751751

752+
// This level of indirection is here because the other child_process methods
753+
// call spawn internally but should use different cancellation logic.
754+
function spawnWithSignal(file, args, options) {
755+
const child = spawn(file, args, options);
756+
757+
if (options && options.signal) {
758+
// Validate signal, if present
759+
validateAbortSignal(options.signal, 'options.signal');
760+
function kill() {
761+
if (child._handle) {
762+
child._handle.kill('SIGTERM');
763+
child.emit('error', new AbortError());
764+
}
765+
}
766+
if (options.signal.aborted) {
767+
process.nextTick(kill);
768+
} else {
769+
options.signal.addEventListener('abort', kill);
770+
const remove = () => options.signal.removeEventListener('abort', kill);
771+
child.once('close', remove);
772+
}
773+
}
774+
return child;
775+
}
752776
module.exports = {
753777
_forkChild,
754778
ChildProcess,
@@ -757,6 +781,6 @@ module.exports = {
757781
execFileSync,
758782
execSync,
759783
fork,
760-
spawn,
784+
spawn: spawnWithSignal,
761785
spawnSync
762786
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const cp = require('child_process');
6+
7+
// Verify that passing an AbortSignal works
8+
const controller = new AbortController();
9+
const { signal } = controller;
10+
11+
const echo = cp.spawn('echo', ['fun'], {
12+
encoding: 'utf8',
13+
shell: true,
14+
signal
15+
});
16+
17+
echo.on('error', common.mustCall((e) => {
18+
assert.strictEqual(e.name, 'AbortError');
19+
}));
20+
21+
controller.abort();

0 commit comments

Comments
 (0)