Skip to content

Commit

Permalink
fix: add support for SIGINT on Windows (fixes issue #1720) (#1853)
Browse files Browse the repository at this point in the history
Smashing work by @countzero ❤️
  • Loading branch information
countzero committed Jun 24, 2021
1 parent b307509 commit 500c1b0
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 9 deletions.
Binary file added bin/windows-kill.exe
Binary file not shown.
61 changes: 52 additions & 9 deletions lib/monitor/run.js
Expand Up @@ -5,6 +5,7 @@ var bus = utils.bus;
var childProcess = require('child_process');
var spawn = childProcess.spawn;
var exec = childProcess.exec;
var execSync = childProcess.execSync;
var fork = childProcess.fork;
var watch = require('./watch').watch;
var config = require('../config');
Expand Down Expand Up @@ -305,20 +306,62 @@ function waitForSubProcesses(pid, callback) {
}

function kill(child, signal, callback) {

if (!callback) {
callback = function () { };
callback = noop;
}

if (utils.isWindows) {
// When using CoffeeScript under Windows, child's process is not node.exe
// Instead coffee.cmd is launched, which launches cmd.exe, which starts
// node.exe as a child process child.kill() would only kill cmd.exe, not
// node.exe
// Therefore we use the Windows taskkill utility to kill the process and all
// its children (/T for tree).
// Force kill (/F) the whole child tree (/T) by PID (/PID 123)
exec('taskkill /pid ' + child.pid + ' /T /F');

// We are handling a 'SIGKILL' POSIX signal under Windows the
// same way it is handled on a UNIX system: We are performing
// a hard shutdown without waiting for the process to clean-up.
if (signal === 'SIGKILL') {

debug('terminating process group by force: %s', child.pid);

// We are using the taskkill utility to terminate the whole
// process group ('/t') of the child ('/pid') by force ('/f').
// We need to end all sub processes, because the 'child'
// process in this context is actually a cmd.exe wrapper.
exec(`taskkill /f /t /pid ${child.pid}`);
callback();
return;
}

// We are using the Windows Management Instrumentation Command-line
// (wmic.exe) to resolve the sub-child process identifier, because the
// 'child' process in this context is actually a cmd.exe wrapper.
// We want to send the termination signal directly to the node process.
// The '2> nul' silences the no process found error message.
const resultBuffer = execSync(
`wmic process where (ParentProcessId=${child.pid}) get ProcessId 2> nul`
);
const result = resultBuffer.toString().match(/^[0-9]+/m);

// If there is no sub-child process we fall back to the child process.
const processId = Array.isArray(result) ? result[0] : child.pid;

debug('sending kill signal SIGINT to process: %s', processId);

// We are using the standalone 'windows-kill' executable to send the
// standard POSIX signal 'SIGINT' to the node process. This fixes #1720.
const windowsKill = path.normalize(
`${process.cwd()}/node_modules/nodemon/bin/windows-kill.exe`
);

// We have to detach the 'windows-kill' execution completely from this
// process group to avoid terminating the nodemon process itself.
// See: https://github.com/alirdn/windows-kill#how-it-works--limitations
//
// Therefore we are using 'start' to create a new cmd.exe context.
// The '/min' option hides the new terminal window and the '/wait'
// option lets the process wait for the command to finish.
execSync(
`start "windows-kill" /min /wait "${windowsKill}" -SIGINT ${processId}`
);
callback();

} else {
// we use psTree to kill the full subtree of nodemon, because when
// spawning processes like `coffee` under the `--debug` flag, it'll spawn
Expand Down

0 comments on commit 500c1b0

Please sign in to comment.