diff --git a/doc/api/child_process.md b/doc/api/child_process.md index e90759b16d3fa8..204d0648b00aed 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -1714,10 +1714,14 @@ may not actually terminate the process. See kill(2) for reference. -On Windows, where POSIX signals do not exist, the `signal` argument will be -ignored except for `'SIGKILL'`, `'SIGTERM'`, `'SIGINT'` and `'SIGQUIT'`, and the -process will always be killed forcefully and abruptly (similar to `'SIGKILL'`). -See [Signal Events][] for more details. +On Windows, where POSIX signals do not exist, signals are handled as follows. +`'SIGKILL'`, `'SIGTERM'`, `'SIGINT'` and `'SIGQUIT'` terminate the process +forcefully and abruptly (similar to `'SIGKILL'`); any other signal whose name is +known on Windows (such as `'SIGHUP'`) does the same. `'SIGWINCH'` is not +terminal and is not coerced: `subprocess.kill()` throws an `ENOSYS` error and +the child keeps running. A signal name that does not exist on Windows (such as +`'SIGSTOP'`) throws an `ERR_UNKNOWN_SIGNAL` error. See [Signal Events][] for more +details. On Linux, child processes of child processes will not be terminated when attempting to kill their parent. This is likely to happen when running a diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 21ccb2a9989b9d..4d9420757122d2 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -351,7 +351,7 @@ class ProcessWrap : public HandleWrap { } #ifdef _WIN32 if (signal != SIGKILL && signal != SIGTERM && signal != SIGINT && - signal != SIGQUIT && signal != 0) { + signal != SIGQUIT && signal != 0 && signal != SIGWINCH) { signal = SIGKILL; } #endif diff --git a/test/parallel/test-child-process-kill-sigwinch.js b/test/parallel/test-child-process-kill-sigwinch.js new file mode 100644 index 00000000000000..e474eefa40b4b3 --- /dev/null +++ b/test/parallel/test-child-process-kill-sigwinch.js @@ -0,0 +1,34 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); + +// SIGWINCH is a non-terminal signal: sending it must not terminate the target +// process. On Windows, kill() must surface ENOSYS instead of coercing +// SIGWINCH into a SIGKILL (which would wrongly terminate the process). +// Refs: https://github.com/nodejs/node/issues/64324 + +const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)']); + +// The process must survive the SIGWINCH; it is only ever torn down by the +// explicit SIGKILL in the cleanup below (after the listener is removed). +child.on('exit', common.mustNotCall('child must survive SIGWINCH')); + +child.on('spawn', common.mustCall(() => { + if (common.isWindows) { + assert.throws(() => child.kill('SIGWINCH'), { code: 'ENOSYS' }); + } else { + assert.strictEqual(child.kill('SIGWINCH'), true); + } + + assert.strictEqual(child.signalCode, null); + assert.strictEqual(child.exitCode, null); + + setTimeout(common.mustCall(() => { + assert.strictEqual(child.signalCode, null); + assert.strictEqual(child.exitCode, null); + + child.removeAllListeners('exit'); + child.kill('SIGKILL'); + }), common.platformTimeout(500)); +}));