UNPKG

weaver

Version:

Interactive process management system

380 lines (341 loc) 9.72 kB
// Generated by CoffeeScript 1.11.0 var EventEmitter, Task, Watcher, assert, fork, mutable, resolve, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; assert = require('assert'); resolve = require('path').resolve; fork = require('child_process').spawn; EventEmitter = require('events').EventEmitter; Watcher = require('./watcher'); /** #* Status codes #* R - restart #* E - error #* D - done (clean exit) #* W - work in progress #* S - stopped */ mutable = { count: true, source: false, cwd: false, env: false, persistent: true, executable: false, timeout: true, runtime: true, watch: true, "arguments": false }; Task = (function(superClass) { extend(Task, superClass); Task.tasks = Object.create(null); Task.create = function(name) { var base; return (base = this.tasks)[name] != null ? base[name] : base[name] = new Task(name); }; Task.destroy = function(name) { if (name in this.tasks) { Watcher.stop(this.tasks[name].watchHandler); delete this.tasks[name]; } }; Task.status = function() { var name, now, ref, status, task; now = Date.now(); status = {}; ref = this.tasks; for (name in ref) { task = ref[name]; status[name] = { count: task.count, source: task.source, restart: task.restart, subtasks: task.subtasks.map(function(subtask) { return { pid: subtask.pid, args: subtask.args, status: subtask.status, uptime: now - subtask.start }; }) }; } return status; }; Task.prototype.log = function() {}; Task.prototype.active = true; function Task(name1) { this.name = name1; this.subtasks = []; this.watchHandler = (function(_this) { return function(error) { if (error) { _this.emit('error', error); } else { _this.restartSubtasks(); } }; })(this); this.on('exit', this.exitHandler); return this; } Task.prototype.upgrade = function(options) { var change, i, index, key, ref, restartRequired, subtask; if (options == null) { options = {}; } restartRequired = false; for (key in mutable) { if (!hasProp.call(mutable, key)) continue; try { assert.deepEqual(this[key], options[key]); } catch (error1) { change = error1; this.upgradeParameter(key, options[key]); restartRequired = restartRequired || !mutable[key]; } } if (restartRequired && this.subtasks.length) { this.log("Restart required for " + this.name + " task group"); this.restartSubtasks(); } for (index = i = 0, ref = this.count || 0; 0 <= ref ? i < ref : i > ref; index = 0 <= ref ? ++i : --i) { subtask = this.subtasks[index]; if (!subtask || (subtask.status === 'R' && !subtask.pid)) { this.spawn(index); } } while (this.subtasks.length > (this.count || 0)) { this.stopSubtask(this.subtasks.pop()); } }; Task.prototype.upgradeParameter = function(key, value) { if (value != null) { this[key] = value; } else { delete this[key]; } switch (key) { case 'watch': Watcher.stop(this.watchHandler); Watcher.start(this.cwd, this.watch || [], this.watchHandler); } }; Task.prototype.spawn = function(id) { var args, argument, binary, eargs, subtask; args = this["arguments"] || []; binary = process.execPath; subtask = { id: id, status: 'W', name: this.name, start: Date.now(), env: this.expandEnv() }; subtask.args = (function() { var i, len, results; results = []; for (i = 0, len = args.length; i < len; i++) { argument = args[i]; if (Array.isArray(argument)) { results.push(argument[id]); } else { results.push(argument); } } return results; })(); eargs = subtask.args.slice(); if (this.executable) { binary = this.source; } else { eargs.unshift(this.source); } subtask.process = fork(binary, eargs, { stdio: 'pipe', cwd: resolve(this.cwd), env: subtask.env }); subtask.pid = subtask.process.pid || 0; if (subtask.pid) { subtask.process.stdout.on('data', this.logHandler.bind(this, subtask.pid)); subtask.process.stderr.on('data', this.logHandler.bind(this, subtask.pid)); subtask.process.once('exit', this.emit.bind(this, 'exit', subtask)); this.log("Task " + subtask.pid + " (" + this.name + ") spawned"); } subtask.process.once('error', (function(_this) { return function(error) { subtask.status = 'E'; subtask.code = 255; subtask.pid = 0; _this.emit('error', error); return _this.log("Failed to start task (" + _this.name + ")"); }; })(this)); this.subtasks[id] = subtask; }; Task.prototype.foreach = function(fn, argument) { var i, len, ref, subtask; ref = this.subtasks; for (i = 0, len = ref.length; i < len; i++) { subtask = ref[i]; fn.call(this, subtask, argument); } }; Task.prototype.killSubtask = function(subtask, signal) { var error; if (subtask && subtask.pid) { try { subtask.process.kill(signal); } catch (error1) { error = error1; this.log("Failed to kill " + subtask.pid + " (" + subtask.name + ") with " + signal); } } }; Task.prototype.stopSubtask = function(subtask) { if (subtask && subtask.pid) { subtask.process.kill('SIGINT'); return setTimeout((function() { if (subtask.pid) { return subtask.process.kill('SIGTERM'); } }), this.timeout || 1000); } }; Task.prototype.restartSubtask = function(subtask) { if (subtask) { subtask.status = 'R'; this.stopSubtask(subtask); } }; Task.prototype.getPID = function(pid) { var i, len, ref, subtask; ref = this.subtasks; for (i = 0, len = ref.length; i < len; i++) { subtask = ref[i]; if (subtask && subtask.pid === pid) { return subtask; } } }; Task.prototype.killPID = function(pid, signal) { if (pid != null) { this.killSubtask(this.getPID(pid), signal); } else { this.killSubtasks(signal); } }; Task.prototype.restartPID = function(pid) { if (pid != null) { this.restartSubtask(this.getPID(pid)); } else { this.restartSubtasks(); } }; Task.prototype.stopPID = function(pid) { if (pid != null) { this.stopSubtask(this.getPID(pid)); } else { this.stopSubtasks(); } }; Task.prototype.killSubtasks = function() { this.foreach(this.killSubtask); }; Task.prototype.restartSubtasks = function() { this.foreach(this.restartSubtask); }; Task.prototype.stopSubtasks = function() { this.foreach(this.stopSubtask); }; Task.prototype.dropSubtasks = function() { if (this.active) { this.active = false; if (!this.activeSubtasks().length) { Task.destroy(this.name); } else { this.stopSubtasks(); } } }; Task.prototype.activeSubtasks = function() { return this.subtasks.filter(function(subtask) { return subtask.pid; }); }; Task.prototype.exitHandler = function(subtask, code, signal) { var elapsed, restartRequired; restartRequired = this.persistent; if (code === null) { this.log("Task " + subtask.pid + " (" + this.name + ") was killed by " + signal); } else { this.log("Task " + subtask.pid + " (" + this.name + ") exited with code " + code); } subtask.pid = 0; subtask.code = code; subtask.signal = signal; delete subtask.process; if (subtask.status !== 'R') { if (code) { subtask.status = 'E'; } else if (signal) { subtask.status = 'S'; } else { subtask.status = 'D'; } } if (restartRequired && code) { elapsed = Date.now() - subtask.start; if (elapsed < (this.runtime || 1000)) { this.log("Restart skipped after " + elapsed + "ms (" + this.name + ")"); restartRequired = false; } } if (subtask.status === 'R') { restartRequired = true; } if (!this.active) { restartRequired = false; if (!this.activeSubtasks().length) { Task.destroy(this.name); } } if (restartRequired) { this.spawn(subtask.id); } }; Task.prototype.logHandler = function(pid, data) { this.log(pid + " (" + this.name + ") " + data); }; Task.prototype.expandEnv = function() { var expanded, key, ref, value; expanded = {}; expanded.HOME = process.env.HOME; expanded.PATH = process.env.PATH; if (!this.executable) { expanded.NODE_PATH = process.env.NODE_PATH; } ref = this.env; for (key in ref) { if (!hasProp.call(ref, key)) continue; value = ref[key]; switch (value) { case true: if (process.env.hasOwnProperty(key)) { expanded[key] = process.env[key]; } break; case false: delete expanded[key]; break; default: expanded[key] = this.env[key]; } } return expanded; }; return Task; })(EventEmitter); module.exports = Task;