Switch HttpShardHandler shard fan-out to virtual threads, remove ParallelHttpShardHandler#4431
Conversation
Submit each shard request as a virtual-thread task that calls lbClient.requestAsync(lbReq).get() to do the request setup, dispatch, and waiting on a single virtual thread. The submitter thread races through the submit loop without paying the per-shard CPU cost (PKI signing, request building, sysprop reads, etc.); those happen in parallel across the virtual threads. Cancellation: cancelAll() interrupts the virtual thread via the executor Future stored in pending. The InterruptedException catch inside the task calls jettyFuture.cancel(true) which is the graceful abort path Jetty's async machinery is designed for, instead of relying on a thread-interrupt of a synchronous send (which races the connection cleanup under stress). ParallelHttpShardHandlerFactory and ParallelHttpShardHandler are removed; the default HttpShardHandlerFactory now provides the parallel-submit / parallel-completion behavior natively, and is the only built-in implementation. Because shardExecutor is a plain Executors.newThreadPerTaskExecutor rather than MDCAwareThreadPoolExecutor, each shard task explicitly propagates the submitter's MDC, marks the virtual thread as a Solr server thread, and replays every registered ExecutorUtil.InheritableThreadLocalProvider on entry. This carries SolrRequestInfo (used by PKI to sign as the calling user), the OpenTelemetry trace Context, and any other registered provider onto the virtual thread, matching the contract MDCAwareThreadPoolExecutor follows for pool threads. ExecutorUtil now exposes getThreadLocalProviders() to allow this iteration from outside the class. The requestAsync call is wrapped in AccessController.doPrivileged so per-request "solr.*" sysprop reads succeed under SecurityManager when the call originates from a virtual thread.
dsmiley
left a comment
There was a problem hiding this comment.
Thanks for working on this :-). I love the code reduction.
I suppose even in Java 21 this should be good since the synchronized virtual-thread issue is limited / can be managed as this is the very outgoing end of Solr. Versus say Solr's front-door.
| for (int i = 0; i < providers.size(); i++) { | ||
| providers.get(i).clean(providerCtx.get(i)); | ||
| } | ||
| MDC.clear(); |
There was a problem hiding this comment.
as this is a virtual thread which aren't pool'ed, why do this cleanup?
| // doPrivileged needed because the request setup reads "solr.*" system properties | ||
| // and otherwise fails under SecurityManager when invoked from a virtual thread. | ||
| @SuppressWarnings("removal") | ||
| CompletableFuture<LBSolrClient.Rsp> tmp = | ||
| AccessController.doPrivileged( | ||
| (PrivilegedExceptionAction<CompletableFuture<LBSolrClient.Rsp>>) |
There was a problem hiding this comment.
ugh; sad, for something so trivial. Why is this not needed seemingly everywhere else?
Any way, we'll likely be removing such things soon.
| // doPrivileged needed because the request setup reads "solr.*" system properties | ||
| // and otherwise fails under SecurityManager when invoked from a virtual thread. | ||
| @SuppressWarnings("removal") | ||
| CompletableFuture<LBSolrClient.Rsp> tmp = |
There was a problem hiding this comment.
why declare tmp instead of directly setting jettyFuture?
|
Please see the description: Experimental Branch Not for contribution review. |
|
And to get ahead of "why create the PR then", it was the simplest way to show something to Houston in the most consumable manner. |
Experimental Branch Not for contribution review.