From 4ee11ed73b7a1b3edc51cfdb8d05b484a7f603cf Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Tue, 15 Sep 2015 16:38:41 -0400 Subject: [PATCH] Crash on reindex - Introduced the concept of a Cancellation error so that services can choose to watch for a cancellation flag. - Services can then send this error back and it will be forwarded to the node. - The node will then know to call shutdown appropriately. --- lib/errors.js | 5 ++++- lib/node.js | 23 +++++++++++++++-------- lib/scaffold/start.js | 6 +++++- lib/services/bitcoind.js | 5 +++++ test/node.unit.js | 5 ++--- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 0a1b6a42..420e4fc1 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -15,11 +15,14 @@ Consensus.BlockExists = createError('BlockExists', Consensus); var Transaction = createError('Transaction', BitcoreNodeError); Transaction.NotFound = createError('NotFound', Transaction); +var Cancellation = createError('Cancellation', BitcoreNodeError); + module.exports = { Error: BitcoreNodeError, NoOutputs: NoOutputs, NoOutput: NoOutput, Wallet: Wallet, Consensus: Consensus, - Transaction: Transaction + Transaction: Transaction, + Cancellation: Cancellation }; diff --git a/lib/node.js b/lib/node.js index b58356a1..6a30ea41 100644 --- a/lib/node.js +++ b/lib/node.js @@ -22,7 +22,7 @@ function Node(config) { this.network = null; this.services = {}; this._unloadedServices = []; - this._loadingServices = {}; + this.started = false; // TODO type check the arguments of config.services if (config.services) { @@ -141,16 +141,15 @@ Node.prototype._startService = function(serviceInfo, callback) { config.node = this; config.name = serviceInfo.name; var service = new serviceInfo.module(config); - self._loadingServices[service.name] = service; + + // include in loaded services + self.services[serviceInfo.name] = service; service.start(function(err) { if (err) { return callback(err); } - // include in loaded services - self.services[serviceInfo.name] = service; - // add API methods var methodData = service.getAPIMethods(); var methodNameConflicts = []; @@ -188,16 +187,24 @@ Node.prototype.start = function(callback) { self._startService(service, next); }, function(err) { - if (err) { + if (err instanceof errors.Cancellation) { + self.cancellation = true; + return callback(err); + } else if (err) { return callback(err); } self.emit('ready'); + self.started = true; callback(); } ); }; Node.prototype.stop = function(callback) { + if (!this.started && !this.cancellation) { + this.pendingStop = true; + return; + } log.info('Beginning shutdown'); var self = this; var services = this.getServiceOrder().reverse(); @@ -208,9 +215,9 @@ Node.prototype.stop = function(callback) { async.eachSeries( services, function(service, next) { - if (self._loadingServices[service.name]) { + if (self.services[service.name]) { log.info('Stopping ' + service.name); - self._loadingServices[service.name].stop(next); + self.services[service.name].stop(next); } else { log.info('Stopping ' + service.name + ' (not started)'); setImmediate(next); diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index 0d24228e..1f193518 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -7,6 +7,7 @@ var bitcore = require('bitcore'); var _ = bitcore.deps._; var $ = bitcore.util.preconditions; var log = index.log; +var errors = index.errors; var child_process = require('child_process'); var fs = require('fs'); var shuttingDown = false; @@ -215,7 +216,10 @@ function start(options) { }); node.start(function(err) { - if(err) { + if (err instanceof errors.Cancellation) { + log.warn('Cancelled start up of all services'); + start.cleanShutdown(process, node); + } else if(err) { log.error('Failed to start services'); if (err.stack) { log.error(err.stack); diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index 2eca3cb4..8f5b7f10 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -9,6 +9,7 @@ var $ = bitcore.util.preconditions; var _ = bitcore.deps._; var index = require('../'); var log = index.log; +var errors = index.errors; var Service = require('../service'); /** @@ -158,6 +159,10 @@ Bitcoin.prototype.start = function(callback) { } if (self._reindex) { var interval = setInterval(function() { + if (self.node.pendingStop) { + clearInterval(interval); + return callback(new errors.Cancellation(), false); + } var percentSynced = bindings.syncPercentage(); log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced); if (percentSynced >= 100) { diff --git a/test/node.unit.js b/test/node.unit.js index d1589152..4216b8cc 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -324,6 +324,7 @@ describe('Bitcore Node', function() { describe('#stop', function() { it('will call stop for each service', function(done) { var node = new Node(baseConfig); + node.started = true; function TestService() {} util.inherits(TestService, BaseService); TestService.prototype.stop = sinon.stub().callsArg(0); @@ -333,10 +334,8 @@ describe('Bitcore Node', function() { ['getData', this, this.getData, 1] ]; }; - var testService = new TestService({node: node}); - node._loadingServices = {'test1': testService}; node.services = { - 'test1': testService + 'test1': new TestService({node: node}) }; node.test2 = {}; node.test2.stop = sinon.stub().callsArg(0);