division
Version:
Simple yet powerful wrapper over node.js cluster API. This module is inspired by impressive, but abandoned project Cluster created by TJ Holowaychuk.
386 lines (359 loc) • 10.2 kB
JavaScript
var EventEmitter, Master, Worker, cluster,
__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; },
__slice = [].slice;
Worker = require('./worker');
cluster = require('cluster');
EventEmitter = require('events').EventEmitter;
module.exports = Master = (function(_super) {
var __define;
__extends(Master, _super);
function Master() {
var __define;
__define = (function(_this) {
return function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return Object.defineProperty.apply(null, [].concat(_this, args));
};
})(this);
__define('pid', {
enumerable: true,
value: process.pid
});
__define('startup', {
enumerable: true,
value: Date.now()
});
__define('workers', {
writable: true,
value: []
});
__define('settings', {
writable: true,
value: {}
});
__define('state', {
writable: true,
value: ''
});
__define('pending', {
writable: true,
value: 0
});
__define('__killed', {
writable: true,
value: 0
});
__define('__incident', {
writable: true,
value: 0
});
__define('__listeners', {
writable: true,
value: []
});
}
__define = function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return Object.defineProperty.apply(null, [].concat(Master.prototype, args));
};
__define('addSignalListener', {
enumerable: true,
value: function(event_or_signal, callback) {
callback = callback.bind(this);
this.__listeners.push([event_or_signal, callback]);
process.on(event_or_signal, callback);
return this;
}
});
__define('increase', {
enumerable: true,
value: function(n) {
if (n == null) {
n = 1;
}
this.emit.call(this, 'increase', n);
this.settings.size += n;
while (n--) {
this.spawn();
}
return this;
}
});
__define('decrease', {
enumerable: true,
value: function(n) {
var index, limit;
if (n == null) {
n = 1;
}
if (n <= 0) {
n = 1;
}
if (n > (limit = this.workers.length)) {
n = limit;
}
this.emit.call(this, 'decrease', n);
this.settings.size -= n;
index = limit - n;
while (n--) {
this.workers[index].close(this.settings.timeout, true);
index++;
}
return this;
}
});
__define('restart', {
enumerable: true,
value: function() {
this.emit.call(this, 'restart');
this.workers.forEach((function(_this) {
return function(worker) {
return worker.close(_this.settings.timeout);
};
})(this));
return this;
}
});
__define('close', {
enumerable: true,
value: function() {
this.emit.call(this, 'close');
this.state = 'graceful';
this.pending = this.workers.length;
this.workers.forEach((function(_this) {
return function(worker) {
return worker.close(_this.settings.timeout);
};
})(this));
this.deregisterEvents();
return this;
}
});
__define('destroy', {
enumerable: true,
value: function() {
this.emit.call(this, 'destroy');
this.state = 'forceful';
this.kill('SIGKILL');
this.deregisterEvents();
return this;
}
});
__define('kill', {
enumerable: true,
value: function(signal) {
if (signal == null) {
signal = 'SIGTERM';
}
this.workers.forEach(function(worker) {
return worker.kill(signal);
});
return this;
}
});
__define('maintenance', {
enumerable: true,
value: function() {
var index, n;
if (this.workers.length < this.settings.size) {
n = this.settings.size - this.workers.length;
while (n--) {
this.spawn();
}
}
if (this.workers.length > this.settings.size) {
n = this.workers.length - this.settings.size;
index = this.settings.size;
while (n--) {
this.workers[index].close(this.settings.timeout, true);
index++;
}
}
return this;
}
});
__define('publish', {
enumerable: true,
value: function() {
var event, id, parameters, _ref;
id = arguments[0], event = arguments[1], parameters = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
if ((_ref = this.worker(id)) != null) {
_ref.publish(event, parameters);
}
return this;
}
});
__define('broadcast', {
enumerable: true,
value: function() {
var event, parameters;
event = arguments[0], parameters = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
this.workers.forEach(function(worker) {
return worker.publish(event, parameters);
});
return this;
}
});
__define('configure', {
value: function(settings) {
this.settings = settings;
this.registerEvents();
this.addSignalListener('SIGCHLD', this.maintenance);
return cluster.setupMaster({
exec: this.settings.path,
args: this.settings.args || process.argv.slice(2),
silent: this.settings.silent || false
});
}
});
__define('spawn', {
value: function(force) {
if (this.workers.length < this.settings.size || force) {
this.workers.push(new Worker());
}
return this;
}
});
__define('registerEvents', {
value: function() {
if (!this.registered) {
cluster.on('fork', (function(_this) {
return function(worker) {
if (worker = _this.worker(worker.id)) {
return _this.emit.call(_this, 'fork', worker);
}
};
})(this));
cluster.on('online', (function(_this) {
return function(worker) {
if (worker = _this.worker(worker.id)) {
return _this.emit.call(_this, 'online', worker);
}
};
})(this));
cluster.on('listening', (function(_this) {
return function(worker, address) {
if (worker = _this.worker(worker.id)) {
return _this.emit.call(_this, 'listening', worker, address);
}
};
})(this));
cluster.on('disconnect', (function(_this) {
return function(worker) {
if (worker = _this.worker(worker.id)) {
_this.emit.call(_this, 'disconnect', worker);
if (!worker.decreased) {
return _this.spawn(true);
}
}
};
})(this));
cluster.on('exit', (function(_this) {
return function(worker, code, signal) {
if (worker = _this.worker(worker.id)) {
_this.emit.call(_this, 'exit', worker, code, signal);
return _this.killed(worker);
}
};
})(this));
cluster.on('error', (function(_this) {
return function(error) {
return _this.emit.call(_this, 'error', error.stack);
};
})(this));
this.on('error', (function(_this) {
return function(error) {
if (!~_this.settings.extensions.indexOf('debug')) {
return process.stderr.write("\n" + error);
}
};
})(this));
}
this.registered = true;
return this;
}
});
__define('deregisterEvents', {
value: function() {
var event, listener, _i, _len, _ref, _ref1;
if (this.registered) {
this.removeAllListeners();
cluster.removeAllListeners();
_ref = this.__listeners;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
_ref1 = _ref[_i], event = _ref1[0], listener = _ref1[1];
process.removeListener(event, listener);
}
this.registered = false;
this.__listeners.length = 0;
}
return this;
}
});
__define('worker', {
value: function(id) {
var worker, _i, _len, _ref;
if (id) {
_ref = this.workers;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
worker = _ref[_i];
if ((worker != null ? worker.id : void 0) === id) {
return worker;
}
}
}
return null;
}
});
__define('cleanup', {
value: function(id) {
var worker, _i, _len, _ref;
if (id) {
_ref = this.workers;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
worker = _ref[_i];
if ((worker != null ? worker.id : void 0) === id || worker === null) {
this.workers.splice(_i, 1);
}
}
}
return this;
}
});
__define('killed', {
value: function(worker) {
var error, message;
if (Date.now() - this.__incident > 300000) {
this.__killed = 0;
this.__incident = Date.now();
}
if (Date.now() - this.__incident < 300000) {
if (++this.__killed > this.settings.kills) {
message = "\n\nDetected over " + this.settings.kills + " worker deaths since last 5 minutes,\nthere is most likely a serious issue with your application.\n\nAborting!\n\n";
if (~this.settings.extensions.indexOf('debug')) {
this.emit.call(this, 'error', "\n" + message);
} else {
error = new Error(message);
error.name = 'Application Error';
throw error;
}
}
}
this.cleanup(worker.id);
switch (this.state) {
case 'graceful':
break;
case 'forceful':
--this.pending || process.nextTick(process.exit);
break;
default:
this.spawn();
}
return this;
}
});
return Master;
})(EventEmitter);