UNPKG

rocksteady

Version:

Drink blazin' electric death, downtime! Fast, zero-downtime apps for production enviroments.

356 lines (333 loc) 10.4 kB
// Generated by CoffeeScript 1.9.1 var Master, cluster, env, events, fs, http, path, ref, utils, 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; cluster = require('cluster'); events = require('events'); fs = require('fs'); http = require('http'); path = require('path'); utils = require('./utils'); env = (ref = process.env.NODE_ENV) != null ? ref : 'development'; Master = (function(superClass) { extend(Master, superClass); function Master(serverModule, options) { var ref1, ref10, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9; if (options == null) { options = {}; } this.serverModule = path.resolve(serverModule); this.forceKillTimeout = (ref1 = options.forceKillTimeout) != null ? ref1 : 30000; this.numWorkers = (ref2 = options.workers) != null ? ref2 : env === 'development' ? 1 : require('os').cpus().length; this.host = (ref3 = options.host) != null ? ref3 : 'localhost'; this.port = (ref4 = options.port) != null ? ref4 : 3000; this.restartCooldown = (ref5 = options.restartCooldown) != null ? ref5 : 2000; this.socketTimeout = (ref6 = options.socketTimeout) != null ? ref6 : 10000; this.watchForChanges = (ref7 = options.watch) != null ? ref7 : env === 'development' ? true : false; this.shuttingDown = false; this.reloading = []; this.workers = {}; this.dropPrivileges = (ref8 = options.dropPrivileges) != null ? ref8 : false; this.runAs = (ref9 = options.runAs) != null ? ref9 : { gid: 'www-data', uid: 'www-data' }; this.setupMaster = (ref10 = options.setupMaster) != null ? ref10 : { exec: __dirname + '/worker.js', silent: false }; cluster.setupMaster(this.setupMaster); switch (typeof options.logger) { case 'function': this.logger = { log: options.logger }; break; case 'object': this.logger = options.logger; break; case 'undefined': this.logger = utils.logger; break; default: this.logger = false; } } Master.prototype.fork = function() { var options, worker; options = { NODE_ENV: env, FORCE_KILL_TIMEOUT: this.forceKillTimeout, HOST: this.host, PORT: this.port, SERVER_MODULE: this.serverModule, SOCKET_TIMEOUT: this.socketTimeout, DROP_PRIVILEGES: this.dropPrivileges, SET_GID: this.runAs.gid, SET_UID: this.runAs.uid }; worker = cluster.fork(options); worker.on('message', (function(_this) { return function(message) { switch (message.type) { case 'error': _this.emit('worker:error', worker, utils.deserialize(message.error)); setTimeout(function() { return _this.fork(); }, _this.restartCooldown); return worker.timeout = setTimeout(function() { worker.kill(); return _this.emit('worker:killed', worker); }, _this.forceKillTimeout); case 'shutdown': return _this.emit('worker:shutdown', worker); case 'watch': return _this.emit('watch', message); } }; })(this)); this.workers[worker.id] = worker; return this.emit('worker:forked', worker); }; Master.prototype.onExit = function(worker, code, signal) { delete this.workers[worker.id]; if (worker.timeout != null) { return clearTimeout(worker.timeout); } if (code !== 0) { setTimeout((function(_this) { return function() { _this.emit('worker:restarting', worker); if (!_this.shuttingDown) { return _this.fork(); } }; })(this), this.restartCooldown); } if (this.shuttingDown && Object.keys(this.workers).length === 0) { return process.exit(0); } }; Master.prototype.onListening = function(worker, address) { this.emit('worker:listening', worker, address); if (this.reloading.length) { return this.reloadNext(); } }; Master.prototype.reloadNext = function() { var worker; if ((worker = this.reloading.shift()) == null) { return this.reloading = false; } worker.reloading = true; worker.timeout = setTimeout((function(_this) { return function() { worker.kill(); return _this.emit('worker:killed', worker); }; })(this), this.forceKillTimeout); worker.send({ type: 'stop' }); return this.fork(); }; Master.prototype.reload = function() { var id, worker; if (this.shuttingDown || this.reloading.length) { return; } this.emit('reloading'); this.reloading = (function() { var ref1, results; ref1 = this.workers; results = []; for (id in ref1) { worker = ref1[id]; if (!worker.reloading) { results.push(worker); } } return results; }).call(this); return this.reloadNext(); }; Master.prototype.shutdown = function() { var id, ref1, worker; if (this.shuttingDown) { process.exit(1); } this.shuttingDown = true; this.emit('shutdown'); ref1 = this.workers; for (id in ref1) { worker = ref1[id]; worker.send({ type: 'stop' }); } return setTimeout((function(_this) { return function() { var ref2; ref2 = _this.workers; for (id in ref2) { worker = ref2[id]; worker.kill(); _this.emit('worker:killed', worker); } return process.exit(1); }; })(this), this.forceKillTimeout); }; Master.prototype.debug = function() { var id, pid, ref1, worker; ref1 = cluster.workers; for (id in ref1) { worker = ref1[id]; pid = worker.process.pid; require('child_process').exec("kill -s 30 " + pid); return; } }; Master.prototype.run = function(cb) { var i, n, ref1; this.once('worker:listening', (function(_this) { return function(worker, address) { _this.running = true; return cb(null); }; })(this)); for (n = i = 1, ref1 = this.numWorkers; 1 <= ref1 ? i <= ref1 : i >= ref1; n = 1 <= ref1 ? ++i : --i) { this.fork(); } cluster.on('exit', (function(_this) { return function(worker, code, signal) { return _this.onExit(worker, code, signal); }; })(this)); cluster.on('listening', (function(_this) { return function(worker, address) { return _this.onListening(worker, address); }; })(this)); process.on('SIGHUP', (function(_this) { return function() { return _this.reload(); }; })(this)); process.on('SIGTERM', (function(_this) { return function() { return _this.shutdown(); }; })(this)); process.on('SIGINT', (function(_this) { return function() { return _this.shutdown(); }; })(this)); process.on('SIGUSR1', (function(_this) { return function() { return _this.debug(); }; })(this)); if (process.stdin.isTTY) { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.setRawMode(true); process.stdin.on('data', (function(_this) { return function(char) { switch (char) { case '\u0003': return _this.shutdown(); case '\u0004': return _this.debug(); case '\u0012': return _this.reload(); } }; })(this)); } if (this.logger) { this.on('worker:error', (function(_this) { return function(worker, err) { return _this.logger.log('error', err, { pid: worker.process.pid }); }; })(this)); this.on('worker:listening', (function(_this) { return function(worker, address) { return _this.logger.log('info', "worker listening on " + address.address + ":" + address.port, { pid: worker.process.pid }); }; })(this)); this.on('worker:killed', (function(_this) { return function(worker) { return _this.logger.log('error', 'worker killed', { pid: worker.process.pid }); }; })(this)); this.on('worker:restarting', (function(_this) { return function(worker) { return _this.logger.log('info', 'worker restarting', { pid: worker.process.pid }); }; })(this)); this.on('shutdown', (function(_this) { return function() { return _this.logger.log('info', 'shutting down'); }; })(this)); this.on('reloading', (function(_this) { return function() { return _this.logger.log('info', 'reloading'); }; })(this)); } if (this.watchForChanges) { return this.watch(); } }; Master.prototype.watch = function(dir) { var bebop, server, watch; if (dir == null) { dir = process.cwd(); } server = http.createServer(); bebop = (require('bebop')).websocket({ server: server }); server.listen(3456); watch = (require('vigil')).watch(dir, (function(_this) { return function(filename, stats, isModule) { var id, ref1, worker; if (_this.logger) { _this.logger.log('info', filename + " modified"); } if (!isModule) { return bebop.modified(filename); } else { _this.once('worker:listening', function(worker, address) { return bebop.modified(filename); }); ref1 = _this.workers; for (id in ref1) { worker = ref1[id]; worker.kill(); } return _this.fork(); } }; })(this)); return this.on('watch', function(arg) { var filename, isDirectory; filename = arg.filename, isDirectory = arg.isDirectory; return watch(filename, !isDirectory); }); }; return Master; })(events.EventEmitter); module.exports = Master; //# sourceMappingURL=master.js.map