weaver
Version:
Interactive process management system
380 lines (341 loc) • 9.72 kB
JavaScript
// 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;