guvnor
Version:
A node process manager that isn't spanners all the way down
135 lines (101 loc) • 3.36 kB
JavaScript
var Autowire = require('wantsit').Autowire
var async = require('async')
var ClusterManager = function () {
this._processService = Autowire
this._os = Autowire
this._cluster = Autowire
this._logger = Autowire
this._parentProcess = Autowire
this._numWorkers = 1
Object.defineProperty(this, 'workers', {
get: function () {
return this._processService.listProcesses()
}.bind(this)
})
}
ClusterManager.prototype.afterPropertiesSet = function (done) {
async.series([
this._findNumWorkers.bind(this),
this._updateWorkers.bind(this)
], done)
}
ClusterManager.prototype._findNumWorkers = function (callback) {
this._numWorkers = parseInt(process.env.GUVNOR_NUM_PROCESSES, 10)
delete process.env.GUVNOR_NUM_PROCESSES
if (isNaN(this._numWorkers)) {
this._numWorkers = 1
}
callback()
}
ClusterManager.prototype._updateWorkers = function (callback) {
var children = this._processService.listProcesses()
if (children.length > this._numWorkers) {
var tasks = []
// kill some workers
children.splice(this._numWorkers, children.length - this._numWorkers).forEach(function (child) {
this._parentProcess.send('worker:stopping', child)
this._processService.stopProcess(child)
tasks.push(function (callback) {
this._processService.once('worker:exit', function () {
callback()
})
}.bind(this))
}.bind(this))
async.parallel(tasks, function (error) {
if (error) {
this._logger.error(error)
}
process.nextTick(function () {
this._parentProcess.send('cluster:online')
}.bind(this))
}.bind(this))
return callback()
} else if (children.length < this._numWorkers) {
var workers = this._numWorkers - children.length
this._logger.info('Creating %d new worker(s)', workers)
var decrement = function () {
workers--
this._logger.info('%d new worker(s) to go', workers)
if (workers === 0) {
process.nextTick(function () {
this._parentProcess.send('cluster:online')
}.bind(this))
}
}.bind(this)
// create some workers
async.timesSeries(workers, this._createWorker.bind(this), function (error, results) {
if (error) return callback(error)
results.forEach(function (processInfo) {
this._logger.info('Worker started with pid', processInfo.pid)
var listener = function (readyProcessInfo) {
if (readyProcessInfo.id !== processInfo.id) {
return
}
decrement()
this._processService.off('worker:ready', listener)
}.bind(this)
this._processService.on('worker:ready', listener)
}.bind(this))
callback()
}.bind(this))
} else {
// already got the right number of children
callback()
}
}
ClusterManager.prototype._createWorker = function (index, callback) {
this._processService.startProcess(callback)
}
ClusterManager.prototype.setNumWorkers = function (workers, callback) {
if (!workers) {
workers = this._numWorkers
}
this._numWorkers = workers
var maxWorkers = this._os.cpus().length
if (this._numWorkers > maxWorkers) {
this._numWorkers = maxWorkers
}
this._logger.info('Set workers to', this._numWorkers)
this._updateWorkers(callback)
}
module.exports = ClusterManager