-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Description
Is your feature request related to a problem? Please describe.
The problem is that there's no cross-platform way to "gracefully" end a child node process.
By "gracefully" I just mean "not abruptly" and "giving it a chance to finish what it needs to do before exiting" (i.e. what SIGINT & SIGTERM are meant to do on Linux).
I know that Windows itself doesn't support those signals, but Node.js already "offers some emulation with process.kill(), and subprocess.kill()" (reference).
Another way that Node.js emulates signal events on Windows (not documented in the reference, but elsewhere) is by generating a SIGINT event when a ctrl+c is received via stdin (and process.stdin is not in raw mode).
In this event, the SIGINT event is generated, not only in the parent process, but also in child node processes.
Let's call this "non-raw-mode ctrl+c behavior".
See "example script" just below for proof.
example script
Run this script (on Windows or Linux), wait for it to say "child started", then hit ctrl+c.
const child = require('child_process').spawn(
'node',
[
'-e',
"console.log('child started');" +
'setInterval(() => {}, 1000);' +
"process.on('SIGINT', () => {" +
"console.log('child received SIGINT');" +
'process.exit();' +
'})',
],
{ stdio: ['ignore', 'inherit', 'inherit'] },
)
child.on('exit', () => console.log('child exited'))
process.on('SIGINT', () => console.log('parent received SIGINT'))Complete output will look like:
child started
child received SIGINT
parent received SIGINT
child exited
If Node.js is somehow able to generate a SIGINT in child node processes in response to a ctrl+c,
then is it possible for Node.js to generate a SIGINT in a child process when we call childProcess.kill('SIGINT')?
This is my main question. :)
Describe the solution you'd like
In Windows environments, when I have a child process that is another Node.js process, I would like to be able to cause a SIGINT event in the child by calling childProcess.kill('SIGINT') in the parent.
Maybe this could even work by sending a ctrl+c keystroke to the child process's stdin? (This is something I failed to do in userland; see first alternative described below) If it worked this way, then even non-node processes could be gracefully stopped on Windows.
Or maybe it could work by copying from the "non-raw-mode ctrl+c behavior" (whatever Node.js is doing under the hood to cause SIGINT events in child processes on Windows)?
Describe alternatives you've considered
First:
I tried to cause a SIGINT in a child process by writing "\u0003" (utf8 character for ctrl+c) to the child's stdin stream.
Since this is the utf8 character emitted by process.stdin when it receives a ctrl+c in raw mode, I thought it would simulate pressing ctrl+c in the terminal and cause a SIGINT event, but nope.
See "example script" just below for proof.
example script
Run this script (on Windows or Linux), wait for it to say "child started", then hit ctrl+c.
const child = require('child_process').spawn(
'node',
[
'-e',
"console.log('child started');" +
'setInterval(() => {}, 1000);' +
"process.on('SIGINT', () => {" +
"console.log('child received SIGINT');" +
'process.exit();' +
'})',
],
{ stdio: ['pipe', 'inherit', 'inherit'] },
)
child.on('exit', () => console.log('child exited'))
process.on('SIGINT', () => console.log('parent received SIGINT'))
// Set `process.stdin` into raw mode so that it emits a buffer containing `"\u0003"` when ctrl+c is pressed
process.stdin.setRawMode(true)
// Pipe parent stdin to child stdin, so child will receive buffer containing `"\u0003"`.
process.stdin.pipe(child.stdin)
// eventually exit example
setTimeout(() => child.kill('SIGKILL'), 5000)Is this is a bug which could be fixed instead of implementing my suggested new feature?
Second:
We can try to take advantage of the "non-raw-mode ctrl+c behavior" (which will generate SIGINT event in parent & child processes, across platforms), but that has lots of limitations:
- Only works when shutting down from ctrl+c. It doesn't help when we want our parent script to clean up & exit due to it's own decision. It also doesn't help when we just want to end the child process and keep the parent running. In these cases we are left with
childProcess.kill('SIGINT')which kills the process abruptly on Windows. - No way to control the timing. We might want to end one child process only after ending another child process. (And actually that would be great, though not strictly necessary, for my library (which currently has slightly inaccurate documentation)).
- Requires parent process's stdin to not be in "raw mode". This means:
- We're constrained to one of two ways to work with input from stdin. IMHO ideally if we had to (or wanted to) switch to raw mode of processing input, we would be able to reproduce the behaviors of non-raw mode (i.e. ctrl+c causing SIGINT in parent & children)
- Now we have potential problems shutting down gracefully on Linux:
Since the parent process doesn't know if aSIGINTevent was caused by a ctrl+c in the terminal, or if it was caused by an actualSIGINTsignal from another process, it needs to issue aSIGINT(withchildProcess.kill('SIGINT')) to be sure that the child received at least oneSIGINTevent.
So when shutting down from ctrl+c, child processes will receive 2SIGINTevents, which could uncover bugs in programs that don't handle multiple signals properly.
On Windows we don't have this problem because we know that if the parent process has aSIGINTevent it was from ctrl+c, and in that situation we don't need to issueSIGINTto children.