UNPKG

the-shepherd

Version:

Control a herd of wild processes.

416 lines (370 loc) 12.6 kB
// Generated by CoffeeScript 1.8.0 (function() { var $, Child, Handlebars, Helpers, Http, Opts, Os, Process, Server, Shell, Worker, log, verbose, __hasProp = {}.hasOwnProperty, __extends = 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; }; $ = require('bling'); Os = require("os"); Shell = require('shelljs'); Handlebars = require("handlebars"); Process = require('./process'); Helpers = require('./helpers'); Http = require('./http'); Opts = require('./opts'); log = $.logger("[child]"); verbose = function() { var err, _ref; try { if (Opts.verbose) { return log.apply(null, arguments); } } catch (_error) { err = _error; return log("verbose error:", (_ref = err.stack) != null ? _ref : err); } }; Child = (function() { var toString; function Child(opts, index) { $.extend(this, { opts: opts, index: index, process: null, started: $.extend($.Promise(), { attempts: 0, timeout: null }) }); this.log = $.logger(this.toString()); this.log.verbose = (function(_this) { return function() { var err, _ref; try { if (Opts.verbose) { return _this.log.apply(null, arguments); } } catch (_error) { err = _error; return _this.log("verbose error:", (_ref = err.stack) != null ? _ref : err); } }; })(this); } Child.prototype.start = function() { var cmd, fail, on_data; try { return this.started; } finally { fail = (function(_this) { return function(msg) { return _this.started.reject(msg); }; })(this); if (++this.started.attempts > this.opts.restart.maxAttempts) { fail("too many attempts"); } else { clearTimeout(this.started.timeout); this.started.timeout = setTimeout(((function(_this) { return function() { return _this.started.attempts = 0; }; })(this)), this.opts.restart.maxInterval); log("shell >", cmd = "env " + (this.env()) + " bash -c 'cd " + this.opts.cd + " && " + this.opts.command + "'"); this.process = Shell.exec(cmd, { silent: true, async: true }, $.identity); this.process.on("exit", (function(_this) { return function(err, signal) { return _this.onExit(err, signal); }; })(this)); on_data = (function(_this) { return function(prefix) { if (prefix == null) { prefix = ""; } return function(data) { var line, _i, _len, _ref, _results; _ref = String(data).split(/\n/); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { line = _ref[_i]; if (line.length) { _results.push(_this.log(prefix + line)); } } return _results; }; }; })(this); this.process.stdout.on("data", on_data("")); this.process.stderr.on("data", on_data("(stderr) ")); if (!this.process.pid) { fail("no pid"); } } } }; Child.prototype.stop = function(signal) { var err, p, _ref; try { return p = $.Promise(); } finally { this.started.attempts = Infinity; this.expectedExit = true; if (this.process) { try { Process.killTree(this.process.pid, signal).then(p.resolve, p.reject); } catch (_error) { err = _error; log("Error calling killTree:", (_ref = err.stack) != null ? _ref : err); } } else { p.resolve(); } } }; Child.prototype.restart = function() { var p, restart; try { return p = $.Promise(); } finally { if (this.process == null) { log("Starting fresh child (no existing process)"); this.start().then(p.resolve, p.reject); } else { restart = (function(_this) { return function() { var err, _ref; try { log("Restarting child..."); _this.process = null; _this.started.reset(); _this.started.attempts = 0; return _this.start().then(p.resolve, p.reject); } catch (_error) { err = _error; log("restart error:", (_ref = err.stack) != null ? _ref : err); return p.reject(err); } }; })(this); log("Killing existing process", this.process.pid); this.expectedExit = true; Process.killTree(this.process.pid, "SIGTERM").wait(this.opts.restart.gracePeriod, function(err) { var _ref; try { if (err === "timeout") { log("Child failed to die within " + this.opts.restart.gracePeriod + "ms, using SIGKILL"); return Process.killTree(this.process.pid, "SIGKILL").then(restart, p.reject); } else if (err) { return p.reject(err); } else { return restart(); } } catch (_error) { err = _error; log("restart error during kill tree:", (_ref = err.stack) != null ? _ref : err); return p.reject(err); } }); } } }; Child.prototype.onExit = function(code, signal) { var err, _ref; try { signal = $.is('number', code) ? code - 128 : Process.getSignalNumber(signal); this.log("Child exited (signal=" + signal + ")", this.expectedExit ? "(expected)" : ""); if (!this.expectedExit) { this.restart(); } return this.expectedExit = false; } catch (_error) { err = _error; return this.log("child.onExit error:", (_ref = err.stack) != null ? _ref : err); } }; Child.prototype.toString = toString = function() { var err, _ref; try { return "" + this.constructor.name + "[" + this.index + "]"; } catch (_error) { err = _error; return log("toString error:", (_ref = err.stack) != null ? _ref : err); } }; Child.prototype.inspect = toString; Child.prototype.env = function() { var err, key, val, _ref; try { return ((function() { var _ref, _results; _ref = this.opts.env; _results = []; for (key in _ref) { val = _ref[key]; if (val != null) { _results.push("" + key + "=\"" + val + "\""); } } return _results; }).call(this)).join(" "); } catch (_error) { err = _error; return log("env error:", (_ref = err.stack) != null ? _ref : err); } }; Child.defaults = function(opts) { opts = $.extend(Object.create(null), { cd: ".", command: "node index.js", count: -1, env: {} }, opts); opts.count = parseInt(opts.count, 10); while (opts.count < 0) { opts.count += Os.cpus().length; } opts.count || (opts.count = 1); opts.restart = $.extend(Object.create(null), { maxAttempts: 5, maxInterval: 10000, gracePeriod: 3000, timeout: 10000 }, opts.restart); opts.git = $.extend(Object.create(null), { enabled: false, cd: ".", remote: "origin", branch: "master", command: "git pull {{remote}} {{branch}} || git merge --abort" }, opts.git); opts.git.command = Handlebars.compile(opts.git.command); opts.git.command.inspect = function(level) { return '"' + opts.git.command({ remote: "{{remote}}", branch: "{{branch}}" }) + '"'; }; return opts; }; return Child; })(); Worker = (function(_super) { var workers; __extends(Worker, _super); Http.get("/workers", function(req, res) { var worker; return res.pass("[" + (((function() { var _i, _len, _ref, _ref1, _results; _results = []; for (_i = 0, _len = workers.length; _i < _len; _i++) { worker = workers[_i]; _results.push("[" + ((_ref = (_ref1 = worker.process) != null ? _ref1.pid : void 0) != null ? _ref : "DEAD") + ", " + worker.port + "]"); } return _results; })()).join(",\n")) + "]"); }); Http.get("/workers/restart", function(req, res) { var worker, _i, _len; for (_i = 0, _len = workers.length; _i < _len; _i++) { worker = workers[_i]; worker.restart(); } return res.redirect(302, "/workers?restarting"); }); workers = []; function Worker(opts, index) { Child.apply(this, [opts = Worker.defaults(opts), index]); workers.push(this); this.log = $.logger(this.toString()); } Worker.prototype.start = function() { Worker.__super__.start.call(this); return this.started.resolve(); }; Worker.defaults = Child.defaults; return Worker; })(Child); Server = (function(_super) { var servers; __extends(Server, _super); Http.get("/servers", function(req, res) { var port, ret, s, v; ret = "["; for (port in servers) { v = servers[port]; ret += ((function() { var _i, _len, _ref, _ref1, _results; _results = []; for (_i = 0, _len = v.length; _i < _len; _i++) { s = v[_i]; _results.push("[" + ((_ref = (_ref1 = s.process) != null ? _ref1.pid : void 0) != null ? _ref : "DEAD") + ", " + s.port + "]"); } return _results; })()).join(",\n"); } return res.pass(ret + "]"); }); Http.get("/servers/restart", function(req, res) { $.valuesOf(servers).flatten().select('restart').call(); return res.redirect(302, "/servers?restarting"); }); servers = {}; function Server(opts, index) { var _name; Child.apply(this, [opts = Server.defaults(opts), index]); this.port = opts.port + index; this.log = $.logger("(" + this.opts.cd + "):" + this.port); (servers[_name = opts.port] != null ? servers[_name] : servers[_name] = []).push(this); } Server.prototype.start = function() { try { return this.started; } finally { Process.clearCache().findOne({ ports: this.port }).then((function(_this) { return function(owner) { if (owner != null) { _this.log("Killing previous owner of", _this.port, "PID:", owner.pid); return Process.killTree(owner, "SIGKILL").then(function() { return _this.start(); }); } else { Server.__super__.start.call(_this); if (!_this.process) { return _this.started.reject("no process"); } else { verbose("Waiting for port", _this.port, "to be owned by", _this.process.pid); return Helpers.portIsOwned(_this.process.pid, _this.port, _this.opts.restart.timeout).then((function() { verbose("Port " + _this.port + " is successfully owned."); return _this.started.resolve(); }), _this.started.reject); } } }; })(this)); } }; Server.prototype.env = function() { return Server.__super__.env.call(this) + ("" + this.opts.portVariable + "=\"" + this.port + "\""); }; Server.defaults = function(opts) { opts = $.extend({ port: 8001, portVariable: "PORT", poolName: "shepherd_pool" }, Child.defaults(opts)); opts.port = parseInt(opts.port, 10); return opts; }; return Server; })(Child); $.extend(module.exports, { Server: Server, Worker: Worker }); }).call(this);