'use strict'; const sleep = require('mz-modules/sleep'); const awaitEvent = require('await-event'); const pstree = require('ps-tree'); module.exports = function* (subProcess, timeout) { const pid = subProcess.process ? subProcess.process.pid : subProcess.pid; const childPids = yield getChildPids(pid); yield [ killProcess(subProcess, timeout), killChildren(childPids, timeout), ]; }; // kill process, if SIGTERM not work, try SIGKILL function* killProcess(subProcess, timeout) { subProcess.kill('SIGTERM'); yield Promise.race([ awaitEvent(subProcess, 'exit'), sleep(timeout), ]); if (subProcess.killed) return; // SIGKILL: http://man7.org/linux/man-pages/man7/signal.7.html // worker: https://github.com/nodejs/node/blob/master/lib/internal/cluster/worker.js#L22 // subProcess.kill is wrapped to subProcess.destroy, it will wait to disconnected. (subProcess.process || subProcess).kill('SIGKILL'); } // kill all children processes, if SIGTERM not work, try SIGKILL function* killChildren(children, timeout) { if (!children.length) return; kill(children, 'SIGTERM'); const start = Date.now(); // if timeout is 1000, it will check twice. const checkInterval = 400; let unterminated = []; while (Date.now() - start < timeout - checkInterval) { yield sleep(checkInterval); unterminated = getUnterminatedProcesses(children); if (!unterminated.length) return; } kill(unterminated, 'SIGKILL'); } function getChildPids(pid) { return new Promise(resolve => { pstree(pid, (err, children) => { // if get children error, just ignore it if (err) children = []; resolve(children.map(children => parseInt(children.PID))); }); }); } function kill(pids, signal) { for (const pid of pids) { try { process.kill(pid, signal); } catch (_) { // ignore } } } function getUnterminatedProcesses(pids) { return pids.filter(pid => { try { // success means it's still alive process.kill(pid, 0); return true; } catch (err) { // error means it's dead return false; } }); }