diff --git a/lib/control-connection.js b/lib/control-connection.js index 54b3e617..710036c5 100644 --- a/lib/control-connection.js +++ b/lib/control-connection.js @@ -99,6 +99,8 @@ class ControlConnection extends events.EventEmitter { this._topologyChangeTimeout = null; // Timeout used for delayed handling of node status changes this._nodeStatusChangeTimeout = null; + // Timeout used for scheduling reconnect + this._reconnectTimeout = null; if (context && context.borrowHostConnection) { this._borrowHostConnection = context.borrowHostConnection; @@ -475,7 +477,8 @@ class ControlConnection extends events.EventEmitter { if (!this._isShuttingDown) { const delay = this._reconnectionSchedule.next().value; this.log('warning', `ControlConnection could not reconnect, scheduling reconnection in ${delay}ms`); - setTimeout(() => this._refresh(), delay); + clearTimeout(this._reconnectTimeout); + this._reconnectTimeout = setTimeout(() => this._refresh(), delay); this.emit('newConnection', err); } @@ -997,6 +1000,7 @@ class ControlConnection extends events.EventEmitter { // Cancel timers clearTimeout(this._topologyChangeTimeout); clearTimeout(this._nodeStatusChangeTimeout); + clearTimeout(this._reconnectTimeout); } /** diff --git a/test/integration/short/control-connection-tests.js b/test/integration/short/control-connection-tests.js index b4810216..b12e952e 100644 --- a/test/integration/short/control-connection-tests.js +++ b/test/integration/short/control-connection-tests.js @@ -16,6 +16,7 @@ 'use strict'; const assert = require('assert'); const util = require('util'); +const sinon = require('sinon'); const helper = require('../../test-helper'); const Client = require('../../../lib/client.js'); @@ -236,6 +237,54 @@ describe('ControlConnection', function () { assert.strictEqual(helper.lastOctetOf(cc.host), '2'); }); + + it('should stop reconnecting after control connection shutdown is called', async () => { + const options = clientOptions.extend(utils.extend({ pooling: { coreConnectionsPerHost: {}}}, helper.baseOptions)); + const reconnectionDelay = 200; + options.policies.reconnection = new policies.reconnection.ConstantReconnectionPolicy(reconnectionDelay); + const cc = newInstance(options, 1, 1); + disposeAfter(cc); + + await cc.init(); + + assert.ok(cc.host); + assert.strictEqual(helper.lastOctetOf(cc.host), '1'); + const lbp = cc.options.policies.loadBalancing; + + await new Promise(r => lbp.init({ log: utils.noop, options: { localDataCenter: 'dc1' }}, cc.hosts, r)); + + // the control connection host should be local or remote to trigger DOWN events + for (const h of cc.hosts.values()) { + h.setDistance(lbp.getDistance(h)); + await h.warmupPool(); + } + + // stop nodes 1 and 2 and make sure they both go down. + await util.promisify(helper.ccmHelper.stopNode)(1); + await helper.wait.forNodeDown(cc.hosts, 1); + await util.promisify(helper.ccmHelper.stopNode)(2); + await helper.wait.forNodeDown(cc.hosts, 2); + + // check that both hosts are down. + cc.hosts.forEach(h => { + if (helper.lastOctetOf(h) === '1') { + assert.strictEqual(h.isUp(), false); + } + else { + assert.strictEqual(h.isUp(), false); + } + }); + + // start spying on _refresh calls + cc._refresh = sinon.spy(cc._refresh); + + cc.shutdown(); + + // wait for reconnectionDelay with 10% lag + await helper.delayAsync(parseInt(reconnectionDelay * 1.1, 10)); + + assert.ok(cc._refresh.notCalled); + }); }); });