actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
151 lines (133 loc) • 4.64 kB
JavaScript
const cluster = require('cluster')
const readline = require('readline')
const os = require('os')
module.exports = {
name: 'start',
description: 'start this ActionHero server',
example: 'actionhero start --config=[/path/to/config] --title=[processTitle] --daemon',
inputs: {
config: {
required: false,
note: 'path to config.js, defaults to "process.cwd() + \'/\' + config.js". You can also use ENV[ACTIONHERO_CONFIG]'
},
title: {
required: false,
note: 'process title to use for ActionHero\'s ID, ps, log, and pidFile defaults. Must be unique for each member of the cluster. You can also use ENV[ACTIONHERO_TITLE]. Process renaming does not work on OSX/Windows'
},
daemon: {
required: false,
note: 'to fork and run as a new background process defaults to false'
}
},
run: function (api, data, next) {
let state
// number of ms to wait to do a forcible shutdown if actionhero won't stop gracefully
let shutdownTimeout = 1000 * 30
if (process.env.ACTIONHERO_SHUTDOWN_TIMEOUT) {
shutdownTimeout = parseInt(process.env.ACTIONHERO_SHUTDOWN_TIMEOUT)
}
const startServer = function (callback) {
state = 'starting'
if (cluster.isWorker) { process.send({state: state}) }
api._context.start(function (error, apiFromCallback) {
if (error) {
api.log(error)
process.exit(1)
} else {
state = 'started'
if (cluster.isWorker) { process.send({state: state}) }
api = apiFromCallback
checkForInernalStop()
if (typeof callback === 'function') { callback(null, api) }
}
})
}
const stopServer = function (callback) {
state = 'stopping'
if (cluster.isWorker) { process.send({state: state}) }
api._context.stop(function () {
state = 'stopped'
if (cluster.isWorker) { process.send({state: state}) }
api = null
if (typeof callback === 'function') { callback(null, api) }
})
}
const restartServer = function (callback) {
state = 'restarting'
if (cluster.isWorker) { process.send({state: state}) }
api._context.restart(function (error, apiFromCallback) {
if (error) { throw (error) }
state = 'started'
if (cluster.isWorker) { process.send({state: state}) }
api = apiFromCallback
if (typeof callback === 'function') { callback(null, api) }
})
}
const stopProcess = function () {
setTimeout(function () {
throw new Error('process stop timeout reached. terminating now.')
}, shutdownTimeout)
// finalTimer.unref();
stopServer(function () {
process.nextTick(function () {
process.exit()
})
})
}
let checkForInernalStopTimer
const checkForInernalStop = function () {
clearTimeout(checkForInernalStopTimer)
if (api.running !== true && state === 'started') {
process.exit(0)
}
checkForInernalStopTimer = setTimeout(checkForInernalStop, shutdownTimeout)
}
if (cluster.isWorker) {
process.on('message', function (msg) {
if (msg === 'start') {
startServer()
} else if (msg === 'stop') {
stopServer()
} else if (msg === 'stopProcess') {
stopProcess()
// in cluster, we cannot re-bind the port
// so kill this worker, and then let the cluster start a new worker
} else if (msg === 'restart') { stopProcess() }
})
process.on('uncaughtException', function (error) {
let stack
try {
stack = error.stack.split(os.EOL)
} catch (e) {
stack = [error]
}
process.send({uncaughtException: {
message: error.message,
stack: stack
}})
process.nextTick(process.exit)
})
process.on('unhandledRejection', function (reason, p) {
process.send({unhandledRejection: {reason: reason, p: p}})
process.nextTick(process.exit)
})
}
process.on('SIGINT', function () { stopProcess() })
process.on('SIGTERM', function () { stopProcess() })
process.on('SIGUSR2', function () { restartServer() })
if (process.platform === 'win32' && !process.env.IISNODE_VERSION) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.on('SIGINT', function () {
process.emit('SIGINT')
})
}
// start the server!
startServer(function () {
next(false, false)
})
}
}