Skip to content

Switch HttpShardHandler shard fan-out to virtual threads, remove ParallelHttpShardHandler#4431

Draft
markrmiller wants to merge 1 commit into
apache:mainfrom
markrmiller:http-shard-handler-virtual-threads
Draft

Switch HttpShardHandler shard fan-out to virtual threads, remove ParallelHttpShardHandler#4431
markrmiller wants to merge 1 commit into
apache:mainfrom
markrmiller:http-shard-handler-virtual-threads

Conversation

@markrmiller
Copy link
Copy Markdown
Member

Experimental Branch Not for contribution review.

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.
@markrmiller markrmiller requested a review from HoustonPutman May 16, 2026 04:36
@github-actions github-actions Bot added documentation Improvements or additions to documentation client:solrj tests cat:search labels May 16, 2026
@dsmiley dsmiley marked this pull request as draft May 17, 2026 14:00
Copy link
Copy Markdown
Contributor

@dsmiley dsmiley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +358 to +361
for (int i = 0; i < providers.size(); i++) {
providers.get(i).clean(providerCtx.get(i));
}
MDC.clear();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as this is a virtual thread which aren't pool'ed, why do this cleanup?

Comment on lines +316 to +321
// 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>>)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why declare tmp instead of directly setting jettyFuture?

@markrmiller
Copy link
Copy Markdown
Member Author

Please see the description: Experimental Branch Not for contribution review.

@markrmiller
Copy link
Copy Markdown
Member Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cat:search client:solrj documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants