Skip to content

Commit 50f076d

Browse files
committed
child_process: support Symbol.asyncDispose
1 parent 7cd4e70 commit 50f076d

File tree

4 files changed

+70
-18
lines changed

4 files changed

+70
-18
lines changed

doc/api/child_process.md

+11
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,17 @@ setTimeout(() => {
14021402
}, 2000);
14031403
```
14041404

1405+
### `subprocess[Symbol.asyncDispose]()`
1406+
1407+
<!-- YAML
1408+
added: REPLACEME
1409+
-->
1410+
1411+
> Stability: 1 - Experimental
1412+
1413+
Calls [`subprocess.kill()`][] with the `killSignal` option (the default is `'SIGTERM'`).
1414+
and returns a promise that fulfills when the process is closed.
1415+
14051416
### `subprocess.killed`
14061417

14071418
<!-- YAML

lib/child_process.js

+4-17
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ const { Buffer } = require('buffer');
6262
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');
6363

6464
const {
65-
AbortError,
6665
codes: errorCodes,
6766
genericNodeError,
6867
} = require('internal/errors');
@@ -86,6 +85,7 @@ const {
8685
} = require('internal/validators');
8786
const child_process = require('internal/child_process');
8887
const {
88+
abortChildProcess,
8989
getValidStdio,
9090
setupChannel,
9191
ChildProcess,
@@ -343,8 +343,6 @@ function execFile(file, args, options, callback) {
343343
// Validate maxBuffer, if present.
344344
validateMaxBuffer(options.maxBuffer);
345345

346-
options.killSignal = sanitizeKillSignal(options.killSignal);
347-
348346
const child = spawn(file, args, {
349347
cwd: options.cwd,
350348
env: options.env,
@@ -354,6 +352,7 @@ function execFile(file, args, options, callback) {
354352
uid: options.uid,
355353
windowsHide: !!options.windowsHide,
356354
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
355+
killSignal: options.killSignal,
357356
});
358357

359358
let encoding;
@@ -448,7 +447,7 @@ function execFile(file, args, options, callback) {
448447

449448
killed = true;
450449
try {
451-
child.kill(options.killSignal);
450+
child.kill(child.killSignal);
452451
} catch (e) {
453452
ex = e;
454453
exithandler();
@@ -712,18 +711,6 @@ function normalizeSpawnArguments(file, args, options) {
712711
};
713712
}
714713

715-
function abortChildProcess(child, killSignal, reason) {
716-
if (!child)
717-
return;
718-
try {
719-
if (child.kill(killSignal)) {
720-
child.emit('error', new AbortError(undefined, { cause: reason }));
721-
}
722-
} catch (err) {
723-
child.emit('error', err);
724-
}
725-
}
726-
727714
/**
728715
* Spawns a new process using the given `file`.
729716
* @param {string} file
@@ -751,7 +738,7 @@ function spawn(file, args, options) {
751738
validateTimeout(options.timeout);
752739
validateAbortSignal(options.signal, 'options.signal');
753740
const killSignal = sanitizeKillSignal(options.killSignal);
754-
const child = new ChildProcess();
741+
const child = new ChildProcess(killSignal);
755742

756743
debug('spawn', options);
757744
child.spawn(options);

lib/internal/child_process.js

+29-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ const {
99
FunctionPrototypeCall,
1010
ObjectDefineProperty,
1111
ObjectSetPrototypeOf,
12+
Promise,
1213
ReflectApply,
1314
StringPrototypeSlice,
1415
Symbol,
16+
SymbolAsyncDispose,
1517
Uint8Array,
1618
} = primordials;
1719

1820
const {
21+
AbortError,
1922
errnoException,
2023
codes: {
2124
ERR_INVALID_ARG_TYPE,
@@ -251,13 +254,14 @@ function stdioStringToArray(stdio, channel) {
251254
return options;
252255
}
253256

254-
function ChildProcess() {
257+
function ChildProcess(killSignal) {
255258
FunctionPrototypeCall(EventEmitter, this);
256259

257260
this._closesNeeded = 1;
258261
this._closesGot = 0;
259262
this.connected = false;
260263

264+
this.killSignal = killSignal;
261265
this.signalCode = null;
262266
this.exitCode = null;
263267
this.killed = false;
@@ -516,6 +520,15 @@ ChildProcess.prototype.kill = function(sig) {
516520
return false;
517521
};
518522

523+
ChildProcess.prototype[SymbolAsyncDispose] = function() {
524+
return new Promise((resolve) => {
525+
this.on('exit', () => resolve(null));
526+
if (!this.killed) {
527+
abortChildProcess(this, this.killSignal);
528+
}
529+
});
530+
};
531+
519532

520533
ChildProcess.prototype.ref = function() {
521534
if (this._handle) this._handle.ref();
@@ -1122,8 +1135,23 @@ function spawnSync(options) {
11221135
return result;
11231136
}
11241137

1138+
1139+
function abortChildProcess(child, killSignal, reason) {
1140+
if (!child)
1141+
return;
1142+
try {
1143+
if (child.kill(killSignal)) {
1144+
child.emit('error', reason == null ? new AbortError() : new AbortError(undefined, { cause: reason }));
1145+
}
1146+
} catch (err) {
1147+
child.emit('error', err);
1148+
}
1149+
}
1150+
1151+
11251152
module.exports = {
11261153
ChildProcess,
1154+
abortChildProcess,
11271155
kChannelHandle,
11281156
setupChannel,
11291157
getValidStdio,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const spawn = require('child_process').spawn;
5+
const cat = spawn(common.isWindows ? 'cmd' : 'cat');
6+
7+
cat.stdout.on('end', common.mustCall());
8+
cat.stderr.on('data', common.mustNotCall());
9+
cat.stderr.on('end', common.mustCall());
10+
11+
cat.on('exit', common.mustCall((code, signal) => {
12+
assert.strictEqual(code, null);
13+
assert.strictEqual(signal, 'SIGTERM');
14+
assert.strictEqual(cat.signalCode, 'SIGTERM');
15+
}));
16+
cat.on('error', common.mustCall((err) => {
17+
assert.strictEqual(cat.signalCode, null);
18+
assert.strictEqual(err.name, 'AbortError');
19+
}));
20+
21+
assert.strictEqual(cat.signalCode, null);
22+
assert.strictEqual(cat.killed, false);
23+
cat[Symbol.asyncDispose]().then(common.mustCall(() => {
24+
assert.strictEqual(cat.signalCode, 'SIGTERM');
25+
}));
26+
assert.strictEqual(cat.killed, true);

0 commit comments

Comments
 (0)