diff --git a/bin/windows-kill.exe b/bin/windows-kill.exe new file mode 100644 index 00000000..98d7d7f7 Binary files /dev/null and b/bin/windows-kill.exe differ diff --git a/lib/monitor/run.js b/lib/monitor/run.js index 50603787..c5f19b01 100644 --- a/lib/monitor/run.js +++ b/lib/monitor/run.js @@ -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'); @@ -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