Skip to content

Feature request: Ability to programmatically cause SIGINT in child node process on Windows #35172

@zenflow

Description

@zenflow

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:

  1. 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.
  2. 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)).
  3. Requires parent process's stdin to not be in "raw mode". This means:
    1. 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)
    2. Now we have potential problems shutting down gracefully on Linux:
      Since the parent process doesn't know if a SIGINT event was caused by a ctrl+c in the terminal, or if it was caused by an actual SIGINT signal from another process, it needs to issue a SIGINT (with childProcess.kill('SIGINT')) to be sure that the child received at least one SIGINT event.
      So when shutting down from ctrl+c, child processes will receive 2 SIGINT events, 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 a SIGINT event it was from ctrl+c, and in that situation we don't need to issue SIGINT to children.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions