Skip to content

Commit

Permalink
child_process: support Symbol.asyncDispose
Browse files Browse the repository at this point in the history
  • Loading branch information
MoLow committed Jun 26, 2023
1 parent 7cd4e70 commit 50f076d
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 18 deletions.
11 changes: 11 additions & 0 deletions doc/api/child_process.md
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,17 @@ setTimeout(() => {
}, 2000);
```

### `subprocess[Symbol.asyncDispose]()`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Calls [`subprocess.kill()`][] with the `killSignal` option (the default is `'SIGTERM'`).
and returns a promise that fulfills when the process is closed.

### `subprocess.killed`

<!-- YAML
Expand Down
21 changes: 4 additions & 17 deletions lib/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const { Buffer } = require('buffer');
const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap');

const {
AbortError,
codes: errorCodes,
genericNodeError,
} = require('internal/errors');
Expand All @@ -86,6 +85,7 @@ const {
} = require('internal/validators');
const child_process = require('internal/child_process');
const {
abortChildProcess,
getValidStdio,
setupChannel,
ChildProcess,
Expand Down Expand Up @@ -343,8 +343,6 @@ function execFile(file, args, options, callback) {
// Validate maxBuffer, if present.
validateMaxBuffer(options.maxBuffer);

options.killSignal = sanitizeKillSignal(options.killSignal);

const child = spawn(file, args, {
cwd: options.cwd,
env: options.env,
Expand All @@ -354,6 +352,7 @@ function execFile(file, args, options, callback) {
uid: options.uid,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
killSignal: options.killSignal,
});

let encoding;
Expand Down Expand Up @@ -448,7 +447,7 @@ function execFile(file, args, options, callback) {

killed = true;
try {
child.kill(options.killSignal);
child.kill(child.killSignal);
} catch (e) {
ex = e;
exithandler();
Expand Down Expand Up @@ -712,18 +711,6 @@ function normalizeSpawnArguments(file, args, options) {
};
}

function abortChildProcess(child, killSignal, reason) {
if (!child)
return;
try {
if (child.kill(killSignal)) {
child.emit('error', new AbortError(undefined, { cause: reason }));
}
} catch (err) {
child.emit('error', err);
}
}

/**
* Spawns a new process using the given `file`.
* @param {string} file
Expand Down Expand Up @@ -751,7 +738,7 @@ function spawn(file, args, options) {
validateTimeout(options.timeout);
validateAbortSignal(options.signal, 'options.signal');
const killSignal = sanitizeKillSignal(options.killSignal);
const child = new ChildProcess();
const child = new ChildProcess(killSignal);

debug('spawn', options);
child.spawn(options);
Expand Down
30 changes: 29 additions & 1 deletion lib/internal/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ const {
FunctionPrototypeCall,
ObjectDefineProperty,
ObjectSetPrototypeOf,
Promise,
ReflectApply,
StringPrototypeSlice,
Symbol,
SymbolAsyncDispose,
Uint8Array,
} = primordials;

const {
AbortError,
errnoException,
codes: {
ERR_INVALID_ARG_TYPE,
Expand Down Expand Up @@ -251,13 +254,14 @@ function stdioStringToArray(stdio, channel) {
return options;
}

function ChildProcess() {
function ChildProcess(killSignal) {
FunctionPrototypeCall(EventEmitter, this);

this._closesNeeded = 1;
this._closesGot = 0;
this.connected = false;

this.killSignal = killSignal;
this.signalCode = null;
this.exitCode = null;
this.killed = false;
Expand Down Expand Up @@ -516,6 +520,15 @@ ChildProcess.prototype.kill = function(sig) {
return false;
};

ChildProcess.prototype[SymbolAsyncDispose] = function() {
return new Promise((resolve) => {
this.on('exit', () => resolve(null));
if (!this.killed) {
abortChildProcess(this, this.killSignal);
}
});
};


ChildProcess.prototype.ref = function() {
if (this._handle) this._handle.ref();
Expand Down Expand Up @@ -1122,8 +1135,23 @@ function spawnSync(options) {
return result;
}


function abortChildProcess(child, killSignal, reason) {
if (!child)
return;
try {
if (child.kill(killSignal)) {
child.emit('error', reason == null ? new AbortError() : new AbortError(undefined, { cause: reason }));
}
} catch (err) {
child.emit('error', err);
}
}


module.exports = {
ChildProcess,
abortChildProcess,
kChannelHandle,
setupChannel,
getValidStdio,
Expand Down
26 changes: 26 additions & 0 deletions test/parallel/test-child-process-destroy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const spawn = require('child_process').spawn;
const cat = spawn(common.isWindows ? 'cmd' : 'cat');

cat.stdout.on('end', common.mustCall());
cat.stderr.on('data', common.mustNotCall());
cat.stderr.on('end', common.mustCall());

cat.on('exit', common.mustCall((code, signal) => {
assert.strictEqual(code, null);
assert.strictEqual(signal, 'SIGTERM');
assert.strictEqual(cat.signalCode, 'SIGTERM');
}));
cat.on('error', common.mustCall((err) => {
assert.strictEqual(cat.signalCode, null);
assert.strictEqual(err.name, 'AbortError');
}));

assert.strictEqual(cat.signalCode, null);
assert.strictEqual(cat.killed, false);
cat[Symbol.asyncDispose]().then(common.mustCall(() => {
assert.strictEqual(cat.signalCode, 'SIGTERM');
}));
assert.strictEqual(cat.killed, true);

0 comments on commit 50f076d

Please sign in to comment.