Permalink
Browse files

refactor master process, add testing

  • Loading branch information...
1 parent 486ccb2 commit ea73f8c4df831058f802b8035acf04e5147bdd83 @haraldrudell committed Dec 22, 2012
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
@@ -1,107 +0,0 @@
-// manager.js
-// manage a child process for the ui
-// © Harald Rudell 2012
-
-exports.receiver = receiver
-exports.setSend = setSend
-
-var send
-var marker = 'master'
-setSend()
-
-var childMap = {}
-
-// http://nodejs.org/api/child_process.html
-var child_process = require('child_process')
-
-function launch(proc, start, debug) {
- var args = Array.isArray(start) ? start : start.split(' ')
- if (debug) args.unshift('--debug-brk')
- proc.didKill = false
- proc.child = child_process.spawn('node', args)
- var thisPid = proc.child.pid
- send({app: proc.app, pid: proc.child.pid, debug: debug})
- 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
- if (isCurrent) {
- proc.child = null
- proc.pendingKill = false
- // code is number
- send({app: proc.app, exit: code, state: (proc.didKill ? 'stop' : 'crash')})
- }
- })
- proc.child.stderr.setEncoding('utf-8')
- proc.child.stdout.setEncoding('utf-8')
- send({app: proc.app, state: (debug ? 'debug' : 'run')})
-
- function log(string) {
- console.log(proc.app, string.slice(0, -1))
- }
-}
-
-function kill(proc) {
- var t
- if (proc.child && proc.child.pid && !proc.didKill) {
- proc.didKill = true
- proc.pendingKill = true
- send({app: proc.app, state: 'stopping'})
- t = Date.now()
- setTimeout(sigResult, 3000)
- // try the nice sigint first (ctrl-break)
- process.kill(proc.child.pid, 'SIGINT')
- }
-
- function sigResult() {
- proc.killTimer = null
- if (proc.pendingKill) {
- 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
- }
- send({map: data})
-}
-
-function receiver(message) {
- var ok
- if (message.app) {
- var proc = childMap[message.app]
- if (!proc) {
- childMap[message.app] = proc = {app: message.app}
- }
- if (message.launch) {
- launch(proc, message.launch, message.debug)
- ok = true
- } else if (message.kill) {
- kill(proc)
- ok = true
- } else if (message.getMap) {
- sendMap()
- ok = true
- } else if (message.del) {
- delete childMap[message.app]
- }
- }
- if (!ok) console.log(marker, 'Bad message', message)
-}
-
-function setSend(f, marker0) {
- send = typeof f == 'function' ? f : badSend
- if (marker0) marker = marker0
-
- function badSend() {
- console.log(marker, 'No ipc available:', Array.prototype.slice.call(arguments))
- }
-}
@@ -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.