diff --git a/lib/cli.js b/lib/cli.js index 0e7e23e4..c730dedc 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -459,96 +459,102 @@ program var log = logger.createLogger('cli') , options = cliutil.lastArg(arguments) - // app triproxy - procutil.fork(__filename, [ - 'triproxy', 'app001' - , '--bind-pub', options.bindAppPub - , '--bind-dealer', options.bindAppDealer - , '--bind-pull', options.bindAppPull - ]) - .catch(function(err) { - log.error('app triproxy died', err.stack) - }) + var procs = [ + // app triproxy + procutil.fork(__filename, [ + 'triproxy', 'app001' + , '--bind-pub', options.bindAppPub + , '--bind-dealer', options.bindAppDealer + , '--bind-pull', options.bindAppPull + ]) - // device triproxy - procutil.fork(__filename, [ - 'triproxy', 'dev001' - , '--bind-pub', options.bindDevPub - , '--bind-dealer', options.bindDevDealer - , '--bind-pull', options.bindDevPull - ]) - .catch(function(err) { - log.error('device triproxy died', err.stack) - }) + // device triproxy + , procutil.fork(__filename, [ + 'triproxy', 'dev001' + , '--bind-pub', options.bindDevPub + , '--bind-dealer', options.bindDevDealer + , '--bind-pull', options.bindDevPull + ]) - // processor one - procutil.fork(__filename, [ - 'processor', 'proc001' - , '--connect-app-dealer', options.bindAppDealer - , '--connect-dev-dealer', options.bindDevDealer - ]) - .catch(function(err) { - log.error('processor 001 died', err.stack) - }) + // processor one + , procutil.fork(__filename, [ + 'processor', 'proc001' + , '--connect-app-dealer', options.bindAppDealer + , '--connect-dev-dealer', options.bindDevDealer + ]) - // processor two - procutil.fork(__filename, [ - 'processor', 'proc002' - , '--connect-app-dealer', options.bindAppDealer - , '--connect-dev-dealer', options.bindDevDealer - ]) - .catch(function(err) { - log.error('processor 002 died', err.stack) - }) + // processor two + , procutil.fork(__filename, [ + 'processor', 'proc002' + , '--connect-app-dealer', options.bindAppDealer + , '--connect-dev-dealer', options.bindDevDealer + ]) - // reaper one - procutil.fork(__filename, [ - 'reaper', 'reaper001' - , '--connect-push', options.bindDevPull - ]) - .catch(function(err) { - log.error('reaper 001 died', err.stack) - }) + // reaper one + , procutil.fork(__filename, [ + 'reaper', 'reaper001' + , '--connect-push', options.bindDevPull + ]) - // provider - procutil.fork(__filename, [ - 'provider' - , '--name', options.provider - , '--connect-sub', options.bindDevPub - , '--connect-push', options.bindDevPull - ].concat(cliutil.allUnknownArgs(arguments))) - .catch(function(err) { - log.error('provider died', err.stack) - }) + // provider + , procutil.fork(__filename, [ + 'provider' + , '--name', options.provider + , '--connect-sub', options.bindDevPub + , '--connect-push', options.bindDevPull + ].concat(cliutil.allUnknownArgs(arguments))) - // auth-mock - procutil.fork(__filename, [ - 'auth-mock' - , '--port', options.authPort - , '--secret', options.authSecret - , '--app-url', util.format('http://localhost:%d/', options.appPort) - ]) - .catch(function(err) { - log.error('auth-mock died', err.stack) - }) + // auth-mock + , procutil.fork(__filename, [ + 'auth-mock' + , '--port', options.authPort + , '--secret', options.authSecret + , '--app-url', util.format('http://localhost:%d/', options.appPort) + ]) - // app - procutil.fork(__filename, [ - 'app' - , '--port', options.appPort - , '--secret', options.authSecret - , '--auth-url', util.format('http://localhost:%d/', options.authPort) - , '--connect-sub', options.bindAppPub - , '--connect-push', options.bindAppPull - ].concat(function() { - var extra = [] - if (options.disableWatch) { - extra.push('--disable-watch') - } - return extra - }())) + // app + , procutil.fork(__filename, [ + 'app' + , '--port', options.appPort + , '--secret', options.authSecret + , '--auth-url', util.format('http://localhost:%d/', options.authPort) + , '--connect-sub', options.bindAppPub + , '--connect-push', options.bindAppPull + ].concat(function() { + var extra = [] + if (options.disableWatch) { + extra.push('--disable-watch') + } + return extra + }())) + ] + + function shutdown() { + log.info('Shutting down all child processes') + procs.forEach(function(proc) { + proc.cancel() + }) + return Promise.settle(procs) + } + + process.on('SIGINT', function() { + log.info('Received SIGINT, waiting for processes to terminate') + }) + + process.on('SIGTERM', function() { + log.info('Received SIGTERM, waiting for processes to terminate') + }) + + Promise.all(procs) + .then(function() { + process.exit(0) + }) .catch(function(err) { - log.error('app died', err.stack) + log.fatal('Child process had an error', err.stack) + return shutdown() + .then(function() { + process.exit(1) + }) }) }) diff --git a/lib/util/procutil.js b/lib/util/procutil.js index 16779103..171871e3 100644 --- a/lib/util/procutil.js +++ b/lib/util/procutil.js @@ -4,9 +4,10 @@ var cp = require('child_process') var Promise = require('bluebird') function ExitError(code) { - Error.call(this, util.format('Exit code "%d"', code)) + Error.call(this) this.name = 'ExitError' this.code = code + this.message = util.format('Exit code "%d"', code) Error.captureStackTrace(this, ExitError) } @@ -17,22 +18,50 @@ module.exports.ExitError = ExitError // Export module.exports.fork = function() { - var args = arguments + var resolver = Promise.defer() + var proc = cp.fork.apply(cp, arguments) - return new Promise(function(resolve, reject) { - var proc = cp.fork.apply(cp, args) + function sigintListener() { + proc.kill('SIGINT') + } - proc.on('error', function(err) { - reject(err) - proc.kill() - }) + function sigtermListener() { + proc.kill('SIGTERM') + } - proc.on('exit', function(code) { - if (code > 0) { - reject(new ExitError(code)) - } - }) + process.on('SIGINT', sigintListener) + process.on('SIGTERM', sigtermListener) + + proc.on('error', function(err) { + resolver.reject(err) + proc.kill() }) + + proc.on('exit', function(code, signal) { + if (signal) { + resolver.resolve(code) + } + else if (code > 0 && code !== 130 && code !== 143) { + resolver.reject(new ExitError(code)) + } + else { + resolver.resolve(code) + } + }) + + return resolver.promise.cancellable() + .finally(function() { + process.removeListener('SIGINT', sigintListener) + process.removeListener('SIGTERM', sigtermListener) + }) + .catch(Promise.CancellationError, function(err) { + return new Promise(function(resolve, reject) { + proc.on('exit', function() { + resolver.resolve() + }) + proc.kill() + }) + }) } // Export