Copilot commented on code in PR #462:
URL: 
https://github.com/apache/cassandra-nodejs-driver/pull/462#discussion_r3270662892


##########
lib/control-connection.js:
##########
@@ -446,13 +441,31 @@ class ControlConnection extends events.EventEmitter {
   }
 
   /**
-   * Acquires a new connection and refreshes topology and keyspace metadata.
+   * Acquires a new connection and refreshes topology and keyspace metadata, 
with protection against concurrent refreshes.
    * <p>When it fails obtaining a connection and there aren't any more hosts, 
it schedules reconnection.</p>
    * <p>When it fails obtaining the metadata, it marks connection and/or host 
unusable and retries using the same
    * iterator from query plan / host list</p>
    * @param {Iterator<Host>} [hostIterator]
    */
   async _refresh(hostIterator) {
+    if (this._refreshInProgress) {
+      return;
+    }
+    this._refreshInProgress = true;
+
+    try {
+      return await this._unsafeDoRefresh(hostIterator)
+    } finally {
+      this._refreshInProgress = false;
+    }

Review Comment:
   When a refresh is already in progress, this returns immediately (resolved 
promise) without waiting for the ongoing refresh. That can cause callers to 
observe `cc.host` / `cc.connection` as `null` right after awaiting 
`_refresh()`, because `_unsafeDoRefresh()` clears them early. Consider storing 
the in-flight refresh promise and returning/awaiting it (or scheduling a 
follow-up refresh) instead of short-circuiting.
   



##########
lib/control-connection.js:
##########
@@ -446,13 +441,31 @@ class ControlConnection extends events.EventEmitter {
   }
 
   /**
-   * Acquires a new connection and refreshes topology and keyspace metadata.
+   * Acquires a new connection and refreshes topology and keyspace metadata, 
with protection against concurrent refreshes.
    * <p>When it fails obtaining a connection and there aren't any more hosts, 
it schedules reconnection.</p>
    * <p>When it fails obtaining the metadata, it marks connection and/or host 
unusable and retries using the same
    * iterator from query plan / host list</p>
    * @param {Iterator<Host>} [hostIterator]
    */
   async _refresh(hostIterator) {
+    if (this._refreshInProgress) {
+      return;
+    }
+    this._refreshInProgress = true;
+
+    try {
+      return await this._unsafeDoRefresh(hostIterator)

Review Comment:
   Missing semicolon after `return await this._unsafeDoRefresh(hostIterator)`; 
this file consistently uses semicolons and this is likely to fail linting.
   



##########
test/integration/short/control-connection-tests.js:
##########
@@ -153,6 +153,25 @@ describe('ControlConnection', function () {
       assert.strictEqual(cc.hosts.length, 1);
     });
 
+    it('should not break when refreshing concurrently', async () => {
+      const cc = newInstance();
+      cc.options.policies.loadBalancing = new 
policies.loadBalancing.RoundRobinPolicy();
+      disposeAfter(cc);
+
+      await cc.init();
+      await new Promise(r => cc.options.policies.loadBalancing.init(null, 
cc.hosts, r));
+
+      const refreshPromises = [];
+      // randomly emit cc._refresh 100 times
+      for (let i = 0; i < 100; i++) {
+        refreshPromises.push(cc._refresh());
+        await helper.delayAsync(~~(Math.random() * 100));
+      }
+      await Promise.all(refreshPromises);

Review Comment:
   This test is nondeterministic and can be flaky/slow: it uses `Math.random()` 
and serial `delayAsync()` up to 100ms over 100 iterations (multi-second 
average). Consider making it deterministic (e.g., fire a fixed batch of 
concurrent `_refresh()` calls without random delays, or use a predictable delay 
pattern) and explicitly waiting for the in-flight refresh to complete before 
asserting on `cc.host`/`cc.connection`.
   



##########
lib/promise-utils.js:
##########
@@ -149,7 +149,7 @@ function times(count, limit, fn) {
  * @returns {undefined}
  */
 function toBackground(promise) {
-  promise.catch(() => {});
+  promise?.catch(() => {});
 }

Review Comment:
   `toBackground()` now accepts `undefined`/`null` (due to optional chaining), 
but the JSDoc still declares `@param {Promise} promise`. Update the type to 
reflect the new behavior (e.g., `{Promise|undefined|null}`) to keep 
documentation accurate.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to