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.
This commit is contained in:
Chris Kleeschulte 2015-09-15 16:38:41 -04:00
parent dc6d0e681c
commit 4ee11ed73b
5 changed files with 31 additions and 13 deletions

View File

@ -15,11 +15,14 @@ Consensus.BlockExists = createError('BlockExists', Consensus);
var Transaction = createError('Transaction', BitcoreNodeError); var Transaction = createError('Transaction', BitcoreNodeError);
Transaction.NotFound = createError('NotFound', Transaction); Transaction.NotFound = createError('NotFound', Transaction);
var Cancellation = createError('Cancellation', BitcoreNodeError);
module.exports = { module.exports = {
Error: BitcoreNodeError, Error: BitcoreNodeError,
NoOutputs: NoOutputs, NoOutputs: NoOutputs,
NoOutput: NoOutput, NoOutput: NoOutput,
Wallet: Wallet, Wallet: Wallet,
Consensus: Consensus, Consensus: Consensus,
Transaction: Transaction Transaction: Transaction,
Cancellation: Cancellation
}; };

View File

@ -22,7 +22,7 @@ function Node(config) {
this.network = null; this.network = null;
this.services = {}; this.services = {};
this._unloadedServices = []; this._unloadedServices = [];
this._loadingServices = {}; this.started = false;
// TODO type check the arguments of config.services // TODO type check the arguments of config.services
if (config.services) { if (config.services) {
@ -141,16 +141,15 @@ Node.prototype._startService = function(serviceInfo, callback) {
config.node = this; config.node = this;
config.name = serviceInfo.name; config.name = serviceInfo.name;
var service = new serviceInfo.module(config); var service = new serviceInfo.module(config);
self._loadingServices[service.name] = service;
// include in loaded services
self.services[serviceInfo.name] = service;
service.start(function(err) { service.start(function(err) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
// include in loaded services
self.services[serviceInfo.name] = service;
// add API methods // add API methods
var methodData = service.getAPIMethods(); var methodData = service.getAPIMethods();
var methodNameConflicts = []; var methodNameConflicts = [];
@ -188,16 +187,24 @@ Node.prototype.start = function(callback) {
self._startService(service, next); self._startService(service, next);
}, },
function(err) { function(err) {
if (err) { if (err instanceof errors.Cancellation) {
self.cancellation = true;
return callback(err);
} else if (err) {
return callback(err); return callback(err);
} }
self.emit('ready'); self.emit('ready');
self.started = true;
callback(); callback();
} }
); );
}; };
Node.prototype.stop = function(callback) { Node.prototype.stop = function(callback) {
if (!this.started && !this.cancellation) {
this.pendingStop = true;
return;
}
log.info('Beginning shutdown'); log.info('Beginning shutdown');
var self = this; var self = this;
var services = this.getServiceOrder().reverse(); var services = this.getServiceOrder().reverse();
@ -208,9 +215,9 @@ Node.prototype.stop = function(callback) {
async.eachSeries( async.eachSeries(
services, services,
function(service, next) { function(service, next) {
if (self._loadingServices[service.name]) { if (self.services[service.name]) {
log.info('Stopping ' + service.name); log.info('Stopping ' + service.name);
self._loadingServices[service.name].stop(next); self.services[service.name].stop(next);
} else { } else {
log.info('Stopping ' + service.name + ' (not started)'); log.info('Stopping ' + service.name + ' (not started)');
setImmediate(next); setImmediate(next);

View File

@ -7,6 +7,7 @@ var bitcore = require('bitcore');
var _ = bitcore.deps._; var _ = bitcore.deps._;
var $ = bitcore.util.preconditions; var $ = bitcore.util.preconditions;
var log = index.log; var log = index.log;
var errors = index.errors;
var child_process = require('child_process'); var child_process = require('child_process');
var fs = require('fs'); var fs = require('fs');
var shuttingDown = false; var shuttingDown = false;
@ -215,7 +216,10 @@ function start(options) {
}); });
node.start(function(err) { 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'); log.error('Failed to start services');
if (err.stack) { if (err.stack) {
log.error(err.stack); log.error(err.stack);

View File

@ -9,6 +9,7 @@ var $ = bitcore.util.preconditions;
var _ = bitcore.deps._; var _ = bitcore.deps._;
var index = require('../'); var index = require('../');
var log = index.log; var log = index.log;
var errors = index.errors;
var Service = require('../service'); var Service = require('../service');
/** /**
@ -158,6 +159,10 @@ Bitcoin.prototype.start = function(callback) {
} }
if (self._reindex) { if (self._reindex) {
var interval = setInterval(function() { var interval = setInterval(function() {
if (self.node.pendingStop) {
clearInterval(interval);
return callback(new errors.Cancellation(), false);
}
var percentSynced = bindings.syncPercentage(); var percentSynced = bindings.syncPercentage();
log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced); log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced);
if (percentSynced >= 100) { if (percentSynced >= 100) {

View File

@ -324,6 +324,7 @@ describe('Bitcore Node', function() {
describe('#stop', function() { describe('#stop', function() {
it('will call stop for each service', function(done) { it('will call stop for each service', function(done) {
var node = new Node(baseConfig); var node = new Node(baseConfig);
node.started = true;
function TestService() {} function TestService() {}
util.inherits(TestService, BaseService); util.inherits(TestService, BaseService);
TestService.prototype.stop = sinon.stub().callsArg(0); TestService.prototype.stop = sinon.stub().callsArg(0);
@ -333,10 +334,8 @@ describe('Bitcore Node', function() {
['getData', this, this.getData, 1] ['getData', this, this.getData, 1]
]; ];
}; };
var testService = new TestService({node: node});
node._loadingServices = {'test1': testService};
node.services = { node.services = {
'test1': testService 'test1': new TestService({node: node})
}; };
node.test2 = {}; node.test2 = {};
node.test2.stop = sinon.stub().callsArg(0); node.test2.stop = sinon.stub().callsArg(0);