[FIX] fix multiple reconnections issue

This commit is contained in:
Ivan Tivonenko
2016-03-22 05:06:17 +02:00
parent ea009f9a84
commit 499b8c8d8b
3 changed files with 83 additions and 4 deletions

View File

@@ -35,6 +35,8 @@ class Connection extends EventEmitter {
this._ledgerVersion = null;
this._availableLedgerVersions = new RangeSet();
this._nextRequestID = 1;
this._retry = 0;
this._retryTimer = null;
}
_updateLedgerVersions(data) {
@@ -95,14 +97,44 @@ class Connection extends EventEmitter {
return this._state === WebSocket.OPEN && this._isReady;
}
_onUnexpectedClose(resolve = function() {}, reject = function() {}) {
_onUnexpectedClose(resolve, reject) {
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
}
this._ws = null;
this._isReady = false;
this.connect().then(resolve, reject);
if (_.isFunction(resolve)) {
// connection was closed before it was properly opened, so we must return
// error to connect's caller
this.connect().then(resolve, reject);
} else {
this.emit('disconnected', true);
this._retryConnect();
}
}
_retryConnect() {
this._retry += 1;
const retryTimeout = (this._retry < 40)
// First, for 2 seconds: 20 times per second
? (1000 / 20)
: (this._retry < 40 + 60)
// Then, for 1 minute: once per second
? (1000)
: (this._retry < 40 + 60 + 60)
// Then, for 10 minutes: once every 10 seconds
? (10 * 1000)
// Then: once every 30 seconds
: (30 * 1000);
this._retryTimer = setTimeout(() => {
this.connect().catch(this._retryConnect.bind(this));
}, retryTimeout);
}
_clearReconnectTimer() {
clearTimeout(this._retryTimer);
this._retryTimer = null;
}
_onOpen() {
@@ -112,6 +144,7 @@ class Connection extends EventEmitter {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
this._retry = 0;
this._ws.on('error', error =>
this.emit('error', 'websocket', error.message, error));
@@ -127,6 +160,7 @@ class Connection extends EventEmitter {
}
_onOpenError(reject, error) {
this._onOpenErrorBound = null;
reject(new NotConnectedError(error && error.message));
}
@@ -174,6 +208,7 @@ class Connection extends EventEmitter {
}
connect() {
this._clearReconnectTimer();
return new Promise((resolve, reject) => {
if (!this._url) {
reject(new ConnectionError(
@@ -208,6 +243,8 @@ class Connection extends EventEmitter {
}
disconnect() {
this._clearReconnectTimer();
this._retry = 0;
return new Promise(resolve => {
if (this._state === WebSocket.CLOSED) {
resolve();
@@ -218,6 +255,7 @@ class Connection extends EventEmitter {
this._ws.once('close', () => {
this._ws = null;
this._isReady = false;
this.emit('disconnected', false);
resolve();
});
this._ws.close();

View File

@@ -193,6 +193,46 @@ describe('Connection', function() {
}, 1);
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.timeout(7000);
const self = this;
function breakConnection() {
setTimeout(() => {
self.mockRippled.close();
setTimeout(() => {
self.mockRippled = setupAPI.createMockRippled(self._mockedServerPort);
}, 1500);
}, 21);
}
let connectsCount = 0;
let disconnectsCount = 0;
this.api.connection.on('disconnected', () => {
disconnectsCount += 1;
});
this.api.connection.on('connected', () => {
connectsCount += 1;
if (connectsCount < 3) {
breakConnection();
}
if (connectsCount === 3) {
if (disconnectsCount !== 3) {
done(new Error('disconnectsCount must be equal to 3 (got ' +
disconnectsCount + ' instead)'));
} else {
done();
}
}
});
breakConnection();
});
it('Multiply connect calls', function() {
return this.api.connect().then(() => {
return this.api.connect();

View File

@@ -1,4 +1,3 @@
'use strict';
const net = require('net');
const RippleAPI = require('ripple-api').RippleAPI;
const RippleAPIBroadcast = require('ripple-api').RippleAPIBroadcast;
@@ -27,6 +26,7 @@ function getFreePort() {
function setupMockRippledConnection(testcase, port) {
return new Promise((resolve, reject) => {
testcase.mockRippled = createMockRippled(port);
testcase._mockedServerPort = port;
testcase.api = new RippleAPI({server: 'ws://localhost:' + port});
testcase.api.connect().then(() => {
testcase.api.once('ledger', () => resolve());
@@ -73,5 +73,6 @@ function teardown(done) {
module.exports = {
setup: setup,
teardown: teardown,
setupBroadcast: setupBroadcast
setupBroadcast: setupBroadcast,
createMockRippled: createMockRippled
};