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 signal support to spawn #36432

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unrelated fix I noticed when reading the docs.

```

### `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 @@ -749,6 +749,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 @@ -757,6 +781,6 @@ module.exports = {
execFileSync,
execSync,
fork,
spawn,
spawn: spawnWithSignal,
spawnSync
};
21 changes: 21 additions & 0 deletions test/parallel/test-child-process-spawn-controller.js
@@ -0,0 +1,21 @@
'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();