Skip to content

Commit 0dfe29e

Browse files
committed
add stronger concurrency control directly in _refresh
1 parent 927a041 commit 0dfe29e

3 files changed

Lines changed: 41 additions & 9 deletions

File tree

lib/control-connection.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class ControlConnection extends events.EventEmitter {
8787
this._addressTranslator = this.options.policies.addressResolution;
8888
this._reconnectionPolicy = this.options.policies.reconnection;
8989
this._reconnectionSchedule = this._reconnectionPolicy.newSchedule();
90+
this._refreshInProgress = false;
9091
this._isShuttingDown = false;
9192

9293
// Reference to the encoder of the last valid connection
@@ -216,7 +217,6 @@ class ControlConnection extends events.EventEmitter {
216217

217218
_setHealthListeners(host, connection) {
218219
const self = this;
219-
let wasRefreshCalled = 0;
220220

221221
function removeListeners() {
222222
host.removeListener('down', downOrIgnoredHandler);
@@ -225,11 +225,6 @@ class ControlConnection extends events.EventEmitter {
225225
}
226226

227227
function startReconnecting(hostDown) {
228-
if (wasRefreshCalled++ !== 0) {
229-
// Prevent multiple calls to reconnect
230-
return;
231-
}
232-
233228
removeListeners();
234229

235230
if (self._isShuttingDown) {
@@ -446,13 +441,31 @@ class ControlConnection extends events.EventEmitter {
446441
}
447442

448443
/**
449-
* Acquires a new connection and refreshes topology and keyspace metadata.
444+
* Acquires a new connection and refreshes topology and keyspace metadata, with protection against concurrent refreshes.
450445
* <p>When it fails obtaining a connection and there aren't any more hosts, it schedules reconnection.</p>
451446
* <p>When it fails obtaining the metadata, it marks connection and/or host unusable and retries using the same
452447
* iterator from query plan / host list</p>
453448
* @param {Iterator<Host>} [hostIterator]
454449
*/
455450
async _refresh(hostIterator) {
451+
if (this._refreshInProgress) {
452+
return;
453+
}
454+
this._refreshInProgress = true;
455+
456+
try {
457+
return await this._unsafeDoRefresh(hostIterator)
458+
} finally {
459+
this._refreshInProgress = false;
460+
}
461+
}
462+
463+
/**
464+
* The actual implementation of the refresh logic, without protection against concurrent executions.
465+
* <p>Should only be used via _refresh.</p>
466+
* @param {Iterator<Host>} [hostIterator]
467+
*/
468+
async _unsafeDoRefresh(hostIterator) {
456469
if (this._isShuttingDown) {
457470
this.log('info', 'The ControlConnection will not be refreshed as the Client is being shutdown');
458471
return;
@@ -499,7 +512,7 @@ class ControlConnection extends events.EventEmitter {
499512
}
500513

501514
// Retry the whole thing with the same query plan
502-
return await this._refresh(hostIterator);
515+
return await this._unsafeDoRefresh(hostIterator);
503516
}
504517

505518
this._reconnectionSchedule = this._reconnectionPolicy.newSchedule();

lib/promise-utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function times(count, limit, fn) {
149149
* @returns {undefined}
150150
*/
151151
function toBackground(promise) {
152-
promise.catch(() => {});
152+
promise?.catch(() => {});
153153
}
154154

155155
/**

test/integration/short/control-connection-tests.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,25 @@ describe('ControlConnection', function () {
153153
assert.strictEqual(cc.hosts.length, 1);
154154
});
155155

156+
it('should not break when refreshing concurrently', async () => {
157+
const cc = newInstance();
158+
cc.options.policies.loadBalancing = new policies.loadBalancing.RoundRobinPolicy();
159+
disposeAfter(cc);
160+
161+
await cc.init();
162+
await new Promise(r => cc.options.policies.loadBalancing.init(null, cc.hosts, r));
163+
164+
const refreshPromises = [];
165+
// randomly emit cc._refresh 100 times
166+
for (let i = 0; i < 100; i++) {
167+
refreshPromises.push(cc._refresh());
168+
await helper.delayAsync(~~(Math.random() * 100));
169+
}
170+
await Promise.all(refreshPromises);
171+
assert.ok(cc.host);
172+
assert.ok(cc.connection);
173+
});
174+
156175
it('should reconnect when host used goes down', async () => {
157176
const options = clientOptions.extend(
158177
utils.extend({ pooling: helper.getPoolingOptions(1, 1, 500) }, helper.baseOptions));

0 commit comments

Comments
 (0)