UNPKG

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
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);