This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 19fbd97f02d SOLR-18142: Fix CloudSolrClient cache state refresh; 
regression. (#4176)
19fbd97f02d is described below

commit 19fbd97f02d01d927405efcdf59176cb56209304
Author: David Smiley <[email protected]>
AuthorDate: Sat Mar 7 15:58:01 2026 -0500

    SOLR-18142: Fix CloudSolrClient cache state refresh; regression. (#4176)
    
    CloudSolrClient- fixed state refresh race; didn't refresh.  Regression from 
9.10.1/10.0
    Found by flaky test: 
CloudSolrClientCacheTest.testStaleStateRetryWaitsAfterSkipFailure
---
 changelog/unreleased/SOLR-18142.yml                |  7 +++
 .../solr/client/solrj/impl/CloudSolrClient.java    | 62 ++++++++++------------
 2 files changed, 34 insertions(+), 35 deletions(-)

diff --git a/changelog/unreleased/SOLR-18142.yml 
b/changelog/unreleased/SOLR-18142.yml
new file mode 100644
index 00000000000..b8b20bfea78
--- /dev/null
+++ b/changelog/unreleased/SOLR-18142.yml
@@ -0,0 +1,7 @@
+title: CloudSolrClient- fixed state refresh race; didn't refresh.  Regression 
from 9.10.1/10.0.
+type: fixed
+authors:
+  - name: David Smiley
+links:
+  - name: SOLR-18142
+    url: https://issues.apache.org/jira/browse/SOLR-18142
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
index 1a41044b793..df7034acf83 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudSolrClient.java
@@ -105,7 +105,7 @@ public abstract class CloudSolrClient extends SolrClient {
   private final boolean directUpdatesToLeadersOnly;
   private final RequestReplicaListTransformerGenerator requestRLTGenerator;
   private final boolean parallelUpdates;
-  private ExecutorService threadPool =
+  private final ExecutorService threadPool =
       ExecutorUtil.newMDCAwareCachedThreadPool(
           new SolrNamedThreadFactory("CloudSolrClient ThreadPool"));
 
@@ -642,9 +642,8 @@ public abstract class CloudSolrClient extends SolrClient {
   public void close() {
     closed = true;
     collectionRefreshes.clear();
-    if (this.threadPool != null && !ExecutorUtil.isShutdown(this.threadPool)) {
+    if (!ExecutorUtil.isShutdown(this.threadPool)) {
       ExecutorUtil.shutdownAndAwaitTermination(this.threadPool);
-      this.threadPool = null;
     }
   }
 
@@ -1658,41 +1657,34 @@ public abstract class CloudSolrClient extends 
SolrClient {
   }
 
   private CompletableFuture<DocCollection> triggerCollectionRefresh(String 
collection) {
-    if (closed) {
-      ExpiringCachedDocCollection cacheEntry = 
collectionStateCache.peek(collection);
-      DocCollection cached = cacheEntry == null ? null : cacheEntry.cached;
-      return CompletableFuture.completedFuture(cached);
-    }
-    return collectionRefreshes.computeIfAbsent(
+    return collectionRefreshes.compute(
         collection,
-        key -> {
-          ExecutorService executor = threadPool;
-          CompletableFuture<DocCollection> future;
-          if (executor == null || ExecutorUtil.isShutdown(executor)) {
-            future = new CompletableFuture<>();
-            try {
-              future.complete(loadDocCollection(key));
-            } catch (Throwable t) {
-              future.completeExceptionally(t);
-            }
+        (key, existingFuture) -> {
+          // A refresh is still in progress; return it.
+          if (existingFuture != null && !existingFuture.isDone()) {
+            return existingFuture;
+          }
+          // No refresh is in-progress, so trigger it.
+
+          if (ExecutorUtil.isShutdown(threadPool)) {
+            assert closed; // see close() for the sequence
+            ExpiringCachedDocCollection cacheEntry = 
collectionStateCache.peek(key);
+            DocCollection cached = cacheEntry == null ? null : 
cacheEntry.cached;
+            return CompletableFuture.completedFuture(cached);
           } else {
-            future =
-                CompletableFuture.supplyAsync(
-                    () -> {
-                      stateRefreshSemaphore.acquireUninterruptibly();
-                      try {
-                        return loadDocCollection(key);
-                      } finally {
-                        stateRefreshSemaphore.release();
-                      }
-                    },
-                    executor);
+            return CompletableFuture.supplyAsync(
+                () -> {
+                  stateRefreshSemaphore.acquireUninterruptibly();
+                  try {
+                    return loadDocCollection(key);
+                  } finally {
+                    stateRefreshSemaphore.release();
+                    // Remove the entry in case of many collections
+                    collectionRefreshes.remove(key);
+                  }
+                },
+                threadPool);
           }
-          future.whenCompleteAsync(
-              (result, error) -> {
-                collectionRefreshes.remove(key, future);
-              });
-          return future;
         });
   }
 

Reply via email to