guvnor
Version:
A node process manager that isn't spanners all the way down
225 lines (185 loc) • 4.93 kB
JavaScript
var util = require('util')
var EventEmitter = require('wildemitter')
var Autowire = require('wantsit').Autowire
var timeoutify = require('timeoutify')
var ManagedProcess = function (info) {
EventEmitter.call(this, {
wildcard: true,
delimiter: ':'
})
this._dnode = Autowire
this._logger = Autowire
this._config = Autowire
// we're going to make the callbacks object non-enumerable
delete this.callbacks
Object.defineProperties(this, {
callbacks: {
value: {},
writable: false
},
_rpc: {
value: {},
writable: false
},
_connected: {
value: false,
writable: true
},
_connecting: {
value: false,
writable: true
},
_remote: {
value: null,
writable: true
}
})
this.workers = []
this.update(info)
// these methods are defined on the ProcessRPC class - must be kept in sync
var methods = [
'kill',
'restart',
'send',
'signal',
'reportStatus',
'dumpHeap',
'forceGc',
'write',
'setClusterWorkers',
'fetchHeapSnapshot',
'removeHeapSnapshot'
]
methods.forEach(function (method) {
this[method] = this._invoke.bind(this, method)
}.bind(this))
}
util.inherits(ManagedProcess, EventEmitter)
ManagedProcess.prototype.update = function (info) {
if (!info) {
return
}
for (var key in info) {
this[key] = info[key]
}
if (!this.cluster) {
// these properties are only present on cluster managers
delete this.workers
delete this.setClusterWorkers
// these are declared on the prototype so can't delete them
this.addWorker = undefined
this.removeWorker = undefined
}
}
ManagedProcess.prototype.disconnect = function (callback) {
if (!this._remote) {
if (callback) {
callback()
}
return
}
if (callback) {
this._remote.once('end', callback)
}
this._connected = false
this._remote.end()
this._remote = null
}
ManagedProcess.prototype.connect = function (callback) {
if (this._connected) {
callback(undefined, this)
return
}
this.once('_connected', callback)
if (!this.socket) {
this.emit('_connected', new Error('No socket defined'))
return
}
// don't try to connect more than once
if (this._connecting) {
return
}
this._connecting = true
this._remote = this._dnode({
// forward received events on
sendEvent: this.emit.bind(this)
}, {
timeout: this._config.guvnor ? this._config.guvnor.rpctimeout : this._config.rpctimeout
})
this._remote.on('error', function (error) {
if (this._connecting) {
this._connecting = false
this.emit('_connected', error)
}
}.bind(this))
this._remote.on('remote', function (remote) {
this._logger.debug('Connected to remote process')
this._connecting = false
this._connected = true
this._bindRemoteMethods(remote)
this.emit('_connected', undefined, this)
}.bind(this))
try {
this._remote.connect(this.socket)
} catch(e) {
callback(e)
}
}
ManagedProcess.prototype._bindRemoteMethods = function (remote) {
for (var method in remote) {
if (method === 'dumpHeap' || method === 'forceGc' || method === 'fetchHeapSnapshot') {
// these are slow so don't timeoutify
this._logger.debug('Exposing remote method %s without timeout', method)
this._rpc[method] = remote[method].bind(remote)
} else {
this._logger.debug('Timeoutifying remote method', method)
this._rpc[method] = timeoutify(remote[method].bind(remote), this._config.guvnor ? this._config.guvnor.timeout : this._config.timeout)
}
}
}
ManagedProcess.prototype._invoke = function (method) {
var args = Array.prototype.slice.call(arguments)
var callback = args[args.length - 1]
if (typeof callback !== 'function') {
callback = function (error) {
if (error) {
throw error
}
}
}
// defer execution if we're not connected yet
if (!this._connected) {
this.connect(function (args, callback, error) {
if (error) {
return callback(error)
}
this._invoke.apply(this, args)
}.bind(this, args, callback))
return
}
// remove the method name from the arguments array
args = args.slice(1)
if (typeof this._rpc[method] !== 'function') {
return callback(new Error('No method ' + method + ' defined!'))
}
try {
this._rpc[method].apply(this._rpc, args)
} catch (error) {
callback(error)
}
}
ManagedProcess.prototype.addWorker = function (worker) {
if (!this.workers.some(function (existingWorker) {
return existingWorker.id === worker.id
})) {
this.workers.push(worker)
}
}
ManagedProcess.prototype.removeWorker = function (worker) {
for (var i = 0; i < this.workers.length; i++) {
if (this.workers[i].id === worker.id) {
this.workers.splice(i, 1)
}
}
}
module.exports = ManagedProcess