rocksteady
Version:
Drink blazin' electric death, downtime! Fast, zero-downtime apps for production enviroments.
356 lines (333 loc) • 10.4 kB
JavaScript
// 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