diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index e4528324..0d24228e 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -9,6 +9,7 @@ var $ = bitcore.util.preconditions; var log = index.log; var child_process = require('child_process'); var fs = require('fs'); +var shuttingDown = false; log.debug = function() {}; @@ -138,30 +139,41 @@ function cleanShutdown(_process, node) { * @param {Node} node */ function registerExitHandlers(_process, node) { - - function exitHandler(options, err) { - if (err) { - log.error('uncaught exception:', err); - if(err.stack) { - log.error(err.stack); - } - node.stop(function(err) { - if(err) { - log.error('Failed to stop services: ' + err); - } - _process.exit(-1); - }); - } - if (options.sigint) { - cleanShutdown(_process, node); - } - } - //catches uncaught exceptions - _process.on('uncaughtException', exitHandler.bind(null, {exit:true})); + _process.on('uncaughtException', exitHandler.bind(null, {exit:true}, _process, node)); //catches ctrl+c event - _process.on('SIGINT', exitHandler.bind(null, {sigint:true})); + _process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node)); +} + +/** + * Will handle all the shutdown tasks that need to take place to ensure a safe exit + * @param {Object} options + * @param {String} options.sigint - The signal given was a SIGINT + * @param {Array} options.exit - The signal given was an uncaughtException + * @param {Object} _process - The Node.js process + * @param {Node} node + * @param {Error} error +*/ +function exitHandler(options, _process, node, err) { + if (err) { + log.error('uncaught exception:', err); + if(err.stack) { + log.error(err.stack); + } + node.stop(function(err) { + if(err) { + log.error('Failed to stop services: ' + err); + } + _process.exit(-1); + }); + } + if (options.sigint) { + if (!shuttingDown) { + shuttingDown = true; + start.cleanShutdown(_process, node); + } + } } /** @@ -254,6 +266,7 @@ function spawnChildProcess(datadir, _process) { module.exports = start; module.exports.registerExitHandlers = registerExitHandlers; +module.exports.exitHandler = exitHandler; module.exports.registerSyncHandlers = registerSyncHandlers; module.exports.setupServices = setupServices; module.exports.spawnChildProcess = spawnChildProcess; diff --git a/test/scaffold/start.unit.js b/test/scaffold/start.unit.js index 3cd8c9de..c955cbf9 100644 --- a/test/scaffold/start.unit.js +++ b/test/scaffold/start.unit.js @@ -304,12 +304,74 @@ describe('#start', function() { }); it('call spawnChildProcess if there is a config option to do so', function() { start(options); + registerSync.callCount.should.equal(1); + registerExit.callCount.should.equal(1); spawn.callCount.should.equal(1); }); it('not call spawnChildProcess if there is not an option to do so', function() { options.config.daemon = false; start(options); + registerSync.callCount.should.equal(1); + registerExit.callCount.should.equal(1); spawn.callCount.should.equal(0); }); }); + describe('#registerExitHandlers', function() { + var stub; + var registerExitHandlers = require('../../lib/scaffold/start').registerExitHandlers; + + before(function() { + stub = sinon.stub(process, 'on'); + }); + + after(function() { + stub.restore(); + }); + + it('should setup two listeners on process when registering exit handlers', function() { + registerExitHandlers(process, {}); + stub.callCount.should.equal(2); + }); + + describe('#exitHandler', function() { + var sandbox; + var cleanShutdown; + var exitHandler; + var logStub; + + before(function() { + sandbox = sinon.sandbox.create(); + var start = require('../../lib/scaffold/start'); + var log = require('../../lib').log; + logStub = sandbox.stub(log, 'error'); + cleanShutdown = sandbox.stub(start, 'cleanShutdown', function() {}); + exitHandler = require('../../lib/scaffold/start').exitHandler; + }); + + after(function() { + sandbox.restore(); + }); + + it('should replace the listener for SIGINT after the first SIGINT is handled', function() { + var options = { sigint: true }; + var node = {}; + exitHandler(options, process, node); + cleanShutdown.callCount.should.equal(1); + exitHandler(options, process, node); + cleanShutdown.callCount.should.equal(1); + }); + + it('should log all errors and stops the services nonetheless', function() { + var options = { sigint: true }; + var stop = sinon.stub(); + var node = { + stop: stop + }; + exitHandler(options, process, node, new Error('some error')); + logStub.callCount.should.equal(2); + stop.callCount.should.equal(1); + }); + + }); + }); });