diff --git a/bin/emulator.js b/bin/emulator.js index 64388b2..40e125c 100644 --- a/bin/emulator.js +++ b/bin/emulator.js @@ -4,7 +4,8 @@ 'use strict'; -var spawn = require('child_process').spawn; +var spawn = require('./spawn'); +var threadKill = require('./thread-kill'); /** * Emulator @@ -17,7 +18,7 @@ function Emulator(task){ Emulator.prototype = { start: function (){ - this.thread = this.exec(this.task.command, { + this.thread = spawn(this.task.command, { env: this.task.env, cwd: this.task.cwd }); @@ -26,53 +27,17 @@ Emulator.prototype = { }, stop: function (){ if (this.thread) { - this.exec('taskkill /t /f /pid ' + this.thread.pid); + var context = this; - ['stdin', 'stdout', 'stderr'].forEach(function (stream){ - this.thread[stream].removeAllListeners(); - }, this); + threadKill(this.thread.pid, function (){ + ['stdin', 'stdout', 'stderr'].forEach(function (stream){ + context.thread[stream].removeAllListeners(); + }); - this.thread = null; + context.thread = null; + }); } - }, - exec: function (){ - var parsed = normalizeExecArgs.apply(null, arguments); - - return spawn(parsed.shell, parsed.args, parsed.options); } }; -/** - * normalize exec args - * @param command - * @param options - * @returns {{cmd: *, shell: *, args: *, options: *}} - */ -function normalizeExecArgs(command, options){ - var shell, args; - - // Make a shallow copy before patching so we don't clobber the user's - // options object. - options = options || {}; - - if (process.platform === 'win32') { - shell = process.env.comspec || 'cmd.exe'; - args = ['/s', '/c', '"' + command + '"']; - options.windowsVerbatimArguments = true; - } else { - shell = '/bin/sh'; - args = ['-c', command]; - } - - if (options.shell) { - shell = options.shell; - } - - return { - shell: shell, - args: args, - options: options - }; -} - module.exports = Emulator; diff --git a/bin/spawn.js b/bin/spawn.js new file mode 100644 index 0000000..6e571b5 --- /dev/null +++ b/bin/spawn.js @@ -0,0 +1,49 @@ +/** + * Created by nuintun on 2016/1/14. + */ + +'use strict'; + +var spawn = require('child_process').spawn; + +/** + * normalize exec args + * @param command + * @param options + * @returns {{cmd: *, shell: *, args: *, options: *}} + */ +function normalizeExecArgs(command, options){ + var shell, args; + + // Make a shallow copy before patching so we don't clobber the user's + // options object. + options = options || {}; + + if (process.platform === 'win32') { + shell = process.env.comspec || 'cmd.exe'; + args = ['/s', '/c', '"' + command + '"']; + options.windowsVerbatimArguments = true; + } else { + shell = '/bin/sh'; + args = ['-c', command]; + } + + if (options.shell) { + shell = options.shell; + } + + return { + shell: shell, + args: args, + options: options + }; +} + +/** + * exec thread + */ +module.exports = function (){ + var parsed = normalizeExecArgs.apply(null, arguments); + + return spawn(parsed.shell, parsed.args, parsed.options); +}; diff --git a/bin/thread-kill.js b/bin/thread-kill.js new file mode 100644 index 0000000..cba6c10 --- /dev/null +++ b/bin/thread-kill.js @@ -0,0 +1,116 @@ +/** + * Created by nuintun on 2016/1/14. + */ + +var spawn = require('./spawn'); +var exec = require('child_process').exec; + +/** + * + * @param pid + * @param signal + * @param callback + */ +module.exports = function (pid, signal, callback){ + var tree = {}; + var pidsToProcess = {}; + + tree[pid] = []; + pidsToProcess[pid] = 1; + + if (typeof signal === 'function') { + signal = 'SIGKILL'; + callback = signal; + } + + switch (process.platform) { + // win32 + case 'win32': + exec('taskkill /pid ' + pid + ' /T /F', callback); + break; + // darwin + case 'darwin': + buildProcessTree(pid, tree, pidsToProcess, function (parentPid){ + return spawn('pgrep', ['-P', parentPid]); + }, function (){ + killAll(tree, signal, callback); + }); + break; + // linux + default: + buildProcessTree(pid, tree, pidsToProcess, function (parentPid){ + return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]); + }, function (){ + killAll(tree, signal, callback); + }); + break; + } +}; + +function killAll(tree, signal, callback){ + var killed = {}; + + try { + Object.keys(tree).forEach(function (pid){ + tree[pid].forEach(function (pidpid){ + if (!killed[pidpid]) { + killPid(pidpid, signal); + killed[pidpid] = 1; + } + }); + if (!killed[pid]) { + killPid(pid, signal); + killed[pid] = 1; + } + }); + } catch (error) { + if (callback) { + return callback(error); + } else { + throw error; + } + } + if (callback) { + return callback(); + } +} + +function killPid(pid, signal){ + try { + process.kill(parseInt(pid, 10), signal); + } + catch (error) { + if (error.code !== 'ESRCH') throw error; + } +} + +function buildProcessTree(parentPid, tree, pidsToProcess, spawnChildProcessesList, callback){ + var allData = ''; + var ps = spawnChildProcessesList(parentPid); + + ps.stdout.on('data', function (data){ + allData += data.toString('ascii'); + }); + + var onClose = function (code){ + delete pidsToProcess[parentPid]; + + if (code != 0) { + // no more parent processes + if (Object.keys(pidsToProcess).length == 0) { + callback(); + } + return; + } + + allData.match(/\d+/g).forEach(function (pid){ + pid = parseInt(pid, 10); + tree[parentPid].push(pid); + tree[pid] = []; + pidsToProcess[pid] = 1; + buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, callback); + }); + }; + + ps.on('close', onClose); +} \ No newline at end of file