-
Notifications
You must be signed in to change notification settings - Fork 840
SOLR-18142: Fix CloudSolrClient cache state refresh; regression. #4176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 @@ protected boolean wasCommError(Throwable t) { | |
| 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 @@ protected DocCollection getDocCollection(String collection, Integer expectedVers | |
| } | ||
|
|
||
| private CompletableFuture<DocCollection> triggerCollectionRefresh(String collection) { | ||
| if (closed) { | ||
| ExpiringCachedDocCollection cacheEntry = collectionStateCache.peek(collection); | ||
| DocCollection cached = cacheEntry == null ? null : cacheEntry.cached; | ||
| return CompletableFuture.completedFuture(cached); | ||
| } | ||
|
Comment on lines
-1661
to
-1665
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found it confusing for this method to have two code paths for the closed scenario. So I centralized it to one spot within the map.compute call. |
||
| 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; | ||
| } | ||
|
Comment on lines
+1662
to
+1666
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the essence of the fix. Everything else in the PR is an improvement but non-critical. |
||
| // 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); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should always remove the same future, by the way. |
||
| } | ||
| }, | ||
| threadPool); | ||
| } | ||
| future.whenCompleteAsync( | ||
| (result, error) -> { | ||
| collectionRefreshes.remove(key, future); | ||
| }); | ||
| return future; | ||
|
Comment on lines
-1691
to
-1695
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's much lighter weight & simpler to read to incorporate this into the single lambda callback to occur after the collection is loaded. I spent a fair amount of time previously trying to assure myself on the nuances of whenComplete vs whenCompleteAsync, and on returning the result of this future vs not, or having the outer method actually do this. Played with a debugger to inspect threads; putting sleep in places and running tests. It was educational but I concluded we can do something much simpler. |
||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not critical but it's needless that this was non-final.