Skip to content

Commit a5c0f39

Browse files
benjamingrtargos
authored andcommitted
child_process: add AbortSignal support
PR-URL: #36308 Backport-PR-URL: #38386 Reviewed-By: Rich Trott <[email protected]>
1 parent 336fb18 commit a5c0f39

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

‎doc/api/child_process.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ lsExample();
250250
<!-- YAML
251251
added: v0.1.91
252252
changes:
253+
- version: REPLACEME
254+
pr-url: https://github.com/nodejs/node/pull/36308
255+
description: AbortSignal support was added.
253256
- version: v8.8.0
254257
pr-url: https://github.com/nodejs/node/pull/15380
255258
description: The `windowsHide` option is supported now.
@@ -277,6 +280,7 @@ changes:
277280
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
278281
shell can be specified as a string. See [Shell requirements][] and
279282
[Default Windows shell][]. **Default:**`false` (no shell).
283+
*`signal`{AbortSignal} allows aborting the execFile using an AbortSignal
280284
*`callback`{Function} Called with the output when process terminates.
281285
*`error`{Error}
282286
*`stdout`{string|Buffer}
@@ -330,6 +334,19 @@ getVersion();
330334
function. Any input containing shell metacharacters may be used to trigger
331335
arbitrary command execution.**
332336

337+
If the `signal` option is enabled, calling `.abort()` on the corresponding
338+
`AbortController` is similar to calling `.kill()` on the child process except
339+
the error passed to the callback will be an `AbortError`:
340+
341+
```js
342+
constcontroller=newAbortController();
343+
const{signal } = controller;
344+
constchild=execFile('node', ['--version'],{signal }, (error) =>{
345+
console.log(error); // an AbortError
346+
});
347+
signal.abort();
348+
```
349+
333350
### `child_process.fork(modulePath[, args][, options])`
334351
<!-- YAML
335352
added: v0.5.0

‎lib/child_process.js‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,24 @@ let debug = require('internal/util/debuglog').debuglog(
4545
);
4646
const{ Buffer }=require('buffer');
4747
const{ Pipe,constants: PipeConstants}=internalBinding('pipe_wrap');
48+
49+
const{
50+
AbortError,
51+
codes: errorCodes,
52+
}=require('internal/errors');
4853
const{
4954
ERR_INVALID_ARG_VALUE,
5055
ERR_CHILD_PROCESS_IPC_REQUIRED,
5156
ERR_CHILD_PROCESS_STDIO_MAXBUFFER,
5257
ERR_INVALID_ARG_TYPE,
53-
ERR_OUT_OF_RANGE
54-
}=require('internal/errors').codes;
58+
ERR_OUT_OF_RANGE,
59+
}=errorCodes;
5560
const{ clearTimeout, setTimeout }=require('timers');
56-
const{ validateString, isInt32 }=require('internal/validators');
61+
const{
62+
validateString,
63+
isInt32,
64+
validateAbortSignal,
65+
}=require('internal/validators');
5766
constchild_process=require('internal/child_process');
5867
const{
5968
getValidStdio,
@@ -232,6 +241,9 @@ function execFile(file /* , args, options, callback */){
232241
// Validate maxBuffer, if present.
233242
validateMaxBuffer(options.maxBuffer);
234243

244+
// Validate signal, if present
245+
validateAbortSignal(options.signal,'options.signal');
246+
235247
options.killSignal=sanitizeKillSignal(options.killSignal);
236248

237249
constchild=spawn(file,args,{
@@ -349,6 +361,20 @@ function execFile(file /* , args, options, callback */){
349361
timeoutId=null;
350362
},options.timeout);
351363
}
364+
if(options.signal){
365+
if(options.signal.aborted){
366+
process.nextTick(()=>kill());
367+
}else{
368+
options.signal.addEventListener('abort',()=>{
369+
if(!ex){
370+
ex=newAbortError();
371+
}
372+
kill();
373+
});
374+
constremove=()=>options.signal.removeEventListener('abort',kill);
375+
child.once('close',remove);
376+
}
377+
}
352378

353379
if(child.stdout){
354380
if(encoding)

‎test/parallel/test-child-process-execfile.js‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Flags: --experimental-abortcontroller
12
'use strict';
23

34
constcommon=require('../common');
@@ -7,6 +8,7 @@ const{getSystemErrorName } = require('util');
78
constfixtures=require('../common/fixtures');
89

910
constfixture=fixtures.path('exit.js');
11+
constechoFixture=fixtures.path('echo.js');
1012
constexecOpts={encoding: 'utf8',shell: true};
1113

1214
{
@@ -45,3 +47,16 @@ const execOpts ={encoding: 'utf8', shell: true };
4547
// Verify the shell option works properly
4648
execFile(process.execPath,[fixture,0],execOpts,common.mustSucceed());
4749
}
50+
51+
{
52+
// Verify that the signal option works properly
53+
constac=newAbortController();
54+
const{ signal }=ac;
55+
56+
constcallback=common.mustCall((err)=>{
57+
assert.strictEqual(err.code,'ABORT_ERR');
58+
assert.strictEqual(err.name,'AbortError');
59+
});
60+
execFile(process.execPath,[echoFixture,0],{ signal },callback);
61+
ac.abort();
62+
}

0 commit comments

Comments
(0)