Skip to content

Commit

Permalink
child_process: add signal support to spawn
Browse files Browse the repository at this point in the history
PR-URL: #36432
Backport-PR-URL: #38386
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
benjamingr authored and targos committed Apr 30, 2021
1 parent 6c08c9d commit 0b691ce
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 3 deletions.
23 changes: 21 additions & 2 deletions doc/api/child_process.md
Expand Up @@ -280,7 +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
* `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 @@ -344,7 +344,7 @@ const { signal } = controller;
const child = execFile('node', ['--version'], { signal }, (error) => {
console.log(error); // an AbortError
});
signal.abort();
controller.abort();
```

### `child_process.fork(modulePath[, args][, options])`
Expand Down Expand Up @@ -424,6 +424,9 @@ The `shell` option available in [`child_process.spawn()`][] is not supported by
<!-- 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 @@ -466,6 +469,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 @@ -572,6 +577,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
26 changes: 25 additions & 1 deletion lib/child_process.js
Expand Up @@ -709,6 +709,30 @@ function sanitizeKillSignal(killSignal) {
}
}

// This level of indirection is here because the other child_process methods
// call spawn internally but should use different cancellation logic.
function spawnWithSignal(file, args, options) {
const child = spawn(file, args, options);

if (options && options.signal) {
// Validate signal, if present
validateAbortSignal(options.signal, 'options.signal');
function kill() {
if (child._handle) {
child._handle.kill('SIGTERM');
child.emit('error', new AbortError());
}
}
if (options.signal.aborted) {
process.nextTick(kill);
} else {
options.signal.addEventListener('abort', kill);
const remove = () => options.signal.removeEventListener('abort', kill);
child.once('close', remove);
}
}
return child;
}
module.exports = {
_forkChild,
ChildProcess,
Expand All @@ -717,6 +741,6 @@ module.exports = {
execFileSync,
execSync,
fork,
spawn,
spawn: spawnWithSignal,
spawnSync
};
22 changes: 22 additions & 0 deletions test/parallel/test-child-process-spawn-controller.js
@@ -0,0 +1,22 @@
// Flags: --experimental-abortcontroller
'use strict';

const common = require('../common');
const assert = require('assert');
const cp = require('child_process');

// Verify that passing an AbortSignal works
const controller = new AbortController();
const { signal } = controller;

const echo = cp.spawn('echo', ['fun'], {
encoding: 'utf8',
shell: true,
signal
});

echo.on('error', common.mustCall((e) => {
assert.strictEqual(e.name, 'AbortError');
}));

controller.abort();

0 comments on commit 0b691ce

Please sign in to comment.