Permalink
Browse files

refactor master process, add testing

  • Loading branch information...
haraldrudell committed Dec 22, 2012
1 parent 486ccb2 commit ea73f8c4df831058f802b8035acf04e5147bdd83
View
28 app.js
@@ -2,30 +2,34 @@
// master process for nodegod
// © Harald Rudell 2012
var establish = require('./lib/master/establish')
var processfinder = require('./lib/master/processfinder')
var uimanager = require('./lib/master/uimanager')
// http://nodejs.org/api/path.html
var path = require('path')
var theSignal = 'SIGUSR2'
var marker = path.basename(__filename, path.extname(__filename))
var fileId = marker + ':' + process.pid
var interMasterSignal = 'SIGUSR2'
var appIndentifier = 'nodegodmaster'
var processName = appIndentifier + ':' + process.pid
// determine if this process should launch ui
console.log(fileId, 'starting')
process.on(theSignal, uimanager.signalHandler)
console.log(processName, 'starting master candidate at', (new Date).toISOString())
process.on(interMasterSignal, uimanager.interMasterSignalHandler)
process.on('uncaughtException', processException)
establish.establish(marker, theSignal, masterResult)
processfinder.isProcessMaster(appIndentifier, interMasterSignal, masterResult)
function masterResult(isMaster) {
if (isMaster) {
console.log(fileId, 'launching ui process')
uimanager.launchUi(fileId, __dirname)
} else console.log(fileId, 'notified the master process')
if (isMaster === true) { // we need to launch the Web ui
console.log(processName, 'confirmed master: launching ui process')
uimanager.launchUi(processName, 'webprocess')
} else { // there is already another nodegod master running, it will launch the web ui
console.log(processName, 'exiting: notified existing master, process id:', isMaster)
}
}
// uncaught exception in this master process: output all information to log
function processException() {
console.log(marker, 'uncaughtException')
console.log(processName, 'uncaughtException')
Array.prototype.slice.call(arguments).forEach(function (value, index) {
console.log(index + ': ', value)
if (value instanceof Error && value.stack) console.log(value.stack)
View
@@ -0,0 +1,145 @@
// appconduit.js
// manage a child process for the ui
// © Harald Rudell 2012
// http://nodejs.org/api/child_process.html
var child_process = require('child_process')
exports.receiveFromUi = receiveFromUi
exports.setSend = setSend
var time3Seconds = 3e3
var graceKillPeriod = time3Seconds
var processName = 'master'
var sendToUi
setSend() // sets send to badSend
var childMap = {}
/*
Receive a command from the ui
message: object
.app: string: the app identifier the message pertains to
(.launch string or array of string: command line arguments)
(.debug: boolean: true if launch to debug mode)
(.kill: boolean: true to kill the child process)
(.getMap: boolean: true to send process map to ui)
(.del boolean: true to delete an app from procMap)
*/
function receiveFromUi(message) {
var ok
if (message.app) {
// get proc
var proc = childMap[message.app]
if (!proc) {
childMap[message.app] = proc = {app: message.app}
}
// execute commands
if (message.launch) {
launchApp(proc, message.launch, message.debug)
ok = true
} else if (message.kill) {
killApp(proc)
ok = true
} else if (message.getMap) {
sendMap()
ok = true
} else if (message.del) {
delete childMap[message.app]
ok = true
}
}
if (!ok) console.log(processName, 'Bad message', message)
}
function launchApp(proc, start, debug) {
// get arguments for node
var args = Array.isArray(start) ? start : start.split(' ')
if (debug) args.unshift('--debug-brk')
// launch app child process
proc.didKill = false
proc.child = child_process.spawn('node', args)
var thisPid = proc.child.pid
sendToUi({app: proc.app, pid: proc.child.pid, debug: debug})
// add app process listeners
proc.child.stdout.on('data', log)
proc.child.stderr.on('data', log)
proc.child.on('exit', function (code) {
var isCurrent = !proc.child || proc.child.pid == thisPid // make sure we did not already relaunch
if (isCurrent) {
proc.child = null
proc.pendingKill = false
if (proc.killTimer) {
clearTimeout(proc.killTimer)
proc.killTimer = null
}
// update state, code is number
sendToUi({app: proc.app, exit: code, state: (proc.didKill ? 'stop' : 'crash')})
}
})
proc.child.stderr.setEncoding('utf-8')
proc.child.stdout.setEncoding('utf-8')
// update state
sendToUi({app: proc.app, state: (debug ? 'debug' : 'run')})
function log(string) {
console.log(proc.app, string.slice(0, -1))
}
}
// kill a possible app child process
function killApp(proc) {
var t
if (proc.child && proc.child.pid && !proc.didKill) {
proc.didKill = true
proc.pendingKill = true
// update app state
sendToUi({app: proc.app, state: 'stopping'})
// attempt graceful kill (ctrl-Break)
t = Date.now()
proc.killTimer = setTimeout(sigResult, graceKillPeriod)
// try the nice sigint first (ctrl-break)
process.kill(proc.child.pid, 'SIGINT')
}
function sigResult() {
proc.killTimer = null
if (proc.pendingKill) { // the process did not die
console.log(marker, proc.app, 'Force kill at: ' + (Date.now() - t) / 1e3)
// now the evil sigterm
process.kill(proc.child.pid)
}
}
}
function sendMap() {
var data = {}
for (var i in childMap) {
var pid
var child = childMap[i].child
if (child && child.pid) pid = child.pid
data[i] = pid
}
sendToUi({map: data})
}
function setSend(f, processName0) {
sendToUi = typeof f == 'function' ? f : badSend
if (processName0) processName = processName0
function badSend() {
console.log(processName, 'No ipc available:', Array.prototype.slice.call(arguments))
}
}
View

This file was deleted.

Oops, something went wrong.
@@ -1,44 +1,45 @@
// establish.js
// establish a nodegod parent process without any dependencies
// processfinder.js
// determine if an app already has a running master process
// © Harald Rudell 2012
exports.establish = establish
exports.isProcessMaster = isProcessMaster
// http://nodejs.org/api/path.html
var path = require('path')
// http://nodejs.org/api/fs.html
var fs = require('fs')
var pidFile
var pidFileName
/*
Establish if this is the master process
marker: string: the id for pid file
appIndentifier: string: the app name eg. 'nodegod'
interMasterSignal: string: the signal name for inter-process communication eg. 'SIGUSR2'
cb(boolean): function:
- true: this is the master process
- false: the master process has been notified to restart the user interface
*/
function establish(marker, theSignal, cb) {
if (!pidFile) pidFile = getFileName(marker)
fs.readFile(pidFile, 'utf-8', pidFromFile)
function isProcessMaster(appIndentifier, interMasterSignal, cb) {
if (!pidFileName) pidFileName = getFileName(appIndentifier)
fs.readFile(pidFileName, 'utf-8', pidFromFile)
function pidFromFile(err, data) {
var pid
if (!err && data) {
var num = parseInt(data)
if (num > 0) { // there was a pid
try { // signal to see if that process still exists
process.kill(num, theSignal)
process.kill(num, interMasterSignal)
pid = num
} catch (e) {
// if process is missing, it's ok
} catch (e) { // if process is missing, it's ok
if (e.errno != 'ESRCH') throw e
}
}
}
if (!pid) { // we are taking over
fs.writeFile(pidFile, String(process.pid), writeResult)
} else cb(false) // we're not master exit
fs.writeFile(pidFileName, String(process.pid), writeResult)
} else cb(pid) // we're not master exit
}
function writeResult(err) {
@@ -49,13 +50,12 @@ function establish(marker, theSignal, cb) {
// helper functions
function getFileName(marker) {
var f = path.basename(__filename, path.extname(__filename))
return path.join(getTmpFolder(), f + '.pid')
function getFileName(appIndentifier) {
return path.join(getTmpFolder(), appIndentifier + '.pid')
}
function getTmpFolder() {
folder = path.join(getHomeFolder(), 'tmp')
var folder = path.join(getHomeFolder(), 'tmp')
if (getType(folder) !== 1) {
folder = process.env.TEMP
if (!folder || getType(folder) !== 1) {
Oops, something went wrong.

0 comments on commit ea73f8c

Please sign in to comment.