diff --git a/changelog/unreleased/http-shard-handler-virtual-threads.yml b/changelog/unreleased/http-shard-handler-virtual-threads.yml new file mode 100644 index 000000000000..1bd67c625341 --- /dev/null +++ b/changelog/unreleased/http-shard-handler-virtual-threads.yml @@ -0,0 +1,6 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: HttpShardHandler now uses Java virtual threads for shard fan-out, replacing CompletableFuture callbacks; the now-equivalent ParallelHttpShardHandlerFactory is removed (switch to the default HttpShardHandlerFactory). +type: changed +authors: + - name: Mark Robert Miller +links: [] diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java index 431652720277..2c28b438c07f 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java @@ -20,16 +20,26 @@ import static org.apache.solr.request.SolrQueryRequest.disallowPartialResults; import java.lang.invoke.MethodHandles; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import net.jcip.annotations.NotThreadSafe; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrResponse; @@ -47,6 +57,7 @@ import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.ExecutorUtil; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.StrUtils; import org.apache.solr.core.CoreDescriptor; @@ -55,6 +66,7 @@ import org.apache.solr.security.AllowListUrlChecker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; /** * Solr's default {@link ShardHandler} implementation; uses Jetty's async HTTP Client APIs for @@ -91,19 +103,20 @@ public class HttpShardHandler extends ShardHandler { private final HttpShardHandlerFactory httpShardHandlerFactory; - protected final ConcurrentMap> - responseFutureMap; + protected final ConcurrentMap> pending; protected final BlockingQueue responses; private final AtomicBoolean canceled = new AtomicBoolean(false); private final Map> shardToURLs; protected LBAsyncSolrClient lbClient; + private final ExecutorService shardExecutor; public HttpShardHandler(HttpShardHandlerFactory httpShardHandlerFactory) { this.httpShardHandlerFactory = httpShardHandlerFactory; this.lbClient = httpShardHandlerFactory.loadbalancer; + this.shardExecutor = httpShardHandlerFactory.shardExecutor; this.responses = new LinkedBlockingQueue<>(); - this.responseFutureMap = new ConcurrentHashMap<>(); + this.pending = new ConcurrentHashMap<>(); // maps "localhost:8983|localhost:7574" to a shuffled // List("http://localhost:8983","http://localhost:7574") @@ -265,40 +278,108 @@ protected void makeShardRequest( SimpleSolrResponse ssr, ShardResponse srsp, long startTimeNS) { - CompletableFuture future = this.lbClient.requestAsync(lbReq); - // Synchronize on canceled, so that we know precisely whether to add it to the responseFutureMap - // or not. - synchronized (canceled) { - if (canceled.get() && !future.isDone()) { - future.cancel(true); - return; - } else { - responseFutureMap.put(srsp, future); + // Capture submitter context now so the shard task (running on a virtual thread) sees the + // submitter's MDC + every registered InheritableThreadLocalProvider (SolrRequestInfo for PKI, + // OTel trace Context, etc.), matching what MDCAwareThreadPoolExecutor does for pool threads. + // Virtual threads aren't pooled so no restore is needed. + final Map submitterMdc = MDC.getCopyOfContextMap(); + final List providers = + ExecutorUtil.getThreadLocalProviders(); + final List> providerCtx; + if (providers.isEmpty()) { + providerCtx = List.of(); + } else { + providerCtx = new ArrayList<>(providers.size()); + for (ExecutorUtil.InheritableThreadLocalProvider p : providers) { + AtomicReference ref = new AtomicReference<>(); + p.store(ref); + providerCtx.add(ref); } } - // Add the callback explicitly after adding the future to the map, because the callback relies - // on the map already having the future. - future.whenComplete( - (LBSolrClient.Rsp rsp, Throwable throwable) -> { - if (rsp != null) { - ssr.nl = rsp.getResponse(); - srsp.setShardAddress(rsp.getServer()); - } else if (throwable != null) { - srsp.setException(throwable); - if (throwable instanceof SolrException) { - srsp.setResponseCode(((SolrException) throwable).code()); - } + + // Build + dispatch the request on the virtual thread (parallel CPU work) and block on the + // returned CompletableFuture there. cancelAll() interrupts the virtual thread; the catch + // below translates that into a graceful jetty-future cancel, which is how Jetty wants to be + // aborted (vs. interrupting a synchronous send mid-flight). + Runnable shardTask = + () -> { + ExecutorUtil.setServerThreadFlag(Boolean.TRUE); + if (submitterMdc != null) { + MDC.setContextMap(submitterMdc); + } + for (int i = 0; i < providers.size(); i++) { + providers.get(i).set(providerCtx.get(i)); } - ssr.elapsedTime = - TimeUnit.MILLISECONDS.convert(System.nanoTime() - startTimeNS, TimeUnit.NANOSECONDS); - // Synchronize on cancelled so this code and cancelAll() cannot happen at the same time - synchronized (canceled) { - // We don't want to add responses after the requests have been canceled - if (responseFutureMap.containsKey(srsp)) { - responses.add(HttpShardHandler.this.transformResponse(sreq, srsp, shard)); + CompletableFuture jettyFuture = null; + try { + try { + // doPrivileged needed because the request setup reads "solr.*" system properties + // and otherwise fails under SecurityManager when invoked from a virtual thread. + @SuppressWarnings("removal") + CompletableFuture tmp = + AccessController.doPrivileged( + (PrivilegedExceptionAction>) + () -> lbClient.requestAsync(lbReq)); + jettyFuture = tmp; + LBSolrClient.Rsp rsp = jettyFuture.get(); + ssr.nl = rsp.getResponse(); + srsp.setShardAddress(rsp.getServer()); + } catch (CancellationException ce) { + // jettyFuture was cancelled; leave srsp without an exception/response. + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + if (jettyFuture != null) { + jettyFuture.cancel(true); + } + } catch (ExecutionException ee) { + Throwable cause = ee.getCause() != null ? ee.getCause() : ee; + srsp.setException(cause); + if (cause instanceof SolrException se) { + srsp.setResponseCode(se.code()); + } + } catch (PrivilegedActionException pae) { + Throwable cause = pae.getCause() != null ? pae.getCause() : pae; + srsp.setException(cause); + if (cause instanceof SolrException se) { + srsp.setResponseCode(se.code()); + } + } + } finally { + ssr.elapsedTime = + TimeUnit.MILLISECONDS.convert( + System.nanoTime() - startTimeNS, TimeUnit.NANOSECONDS); + // Synchronize on canceled so this code and cancelAll() cannot happen at the same time + synchronized (canceled) { + // We don't want to add responses after the requests have been canceled + if (pending.containsKey(srsp)) { + responses.add(HttpShardHandler.this.transformResponse(sreq, srsp, shard)); + } } + for (int i = 0; i < providers.size(); i++) { + providers.get(i).clean(providerCtx.get(i)); + } + MDC.clear(); } - }); + }; + + // Submit cheaply; the coordinator thread races through the loop while CPU-heavy request + // setup happens in parallel on the virtual threads. + synchronized (canceled) { + if (canceled.get()) { + return; + } + try { + Future vtFuture = shardExecutor.submit(shardTask); + pending.put(srsp, vtFuture); + } catch (RejectedExecutionException ree) { + recordShardSubmitError( + srsp, + new SolrException( + SolrException.ErrorCode.SERVER_ERROR, + "Shard executor rejected request for shard: " + shard, + ree)); + } + } } /** Subclasses could modify the request based on the shard */ @@ -349,7 +430,7 @@ private ShardResponse take(boolean bailOnError) { .orElse(previousResponse); } } else { - responseFutureMap.remove(rsp); + pending.remove(rsp); // add response to the response list... we do this after the take() and // not after the completion of "call" so we know when the last response @@ -375,7 +456,7 @@ private ShardResponse take(boolean bailOnError) { } protected boolean responsesPending() { - return !responseFutureMap.isEmpty() || !responses.isEmpty(); + return !pending.isEmpty() || !responses.isEmpty(); } @Override @@ -394,13 +475,12 @@ public void cancelAll() { // We don't want to queue this multiple times if we are already canceled responses.add(CANCELLATION_NOTIFICATION); } - // Cancel all outstanding requests - for (CompletableFuture future : responseFutureMap.values()) { + for (Future future : pending.values()) { if (!future.isDone()) { future.cancel(true); } } - responseFutureMap.clear(); + pending.clear(); } } diff --git a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java index 574a0b266a37..ceaf9db0b85f 100644 --- a/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java +++ b/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java @@ -28,6 +28,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -84,6 +85,13 @@ public class HttpShardHandlerFactory extends ShardHandlerFactory // This executor is initialized in the init method protected ExecutorService commExecutor; + /** + * Virtual-thread-per-task executor used for the per-shard scatter/gather fan-out in {@link + * HttpShardHandler}. Distinct from {@link #commExecutor}, which serves as Jetty's HTTP client + * worker executor. + */ + protected ExecutorService shardExecutor; + protected volatile HttpJettySolrClient defaultClient; protected InstrumentedHttpListenerFactory httpListenerFactory; protected LBAsyncSolrClient loadbalancer; @@ -286,6 +294,10 @@ public void init(PluginInfo info) { // collection as an optimization. see SOLR-11880 for more details false); + this.shardExecutor = + Executors.newThreadPerTaskExecutor( + Thread.ofVirtual().name("httpShardVT-", 0).factory()); + this.httpListenerFactory = new InstrumentedHttpListenerFactory(this.metricNameStrategy); int connectionTimeout = getParameter( @@ -341,16 +353,24 @@ protected T getParameter( @Override public void close() { try { - if (loadbalancer != null) { - loadbalancer.close(); + // Stop the per-shard virtual-thread executor first so in-flight tasks are interrupted before + // the load balancer / HTTP client they depend on are torn down. + if (shardExecutor != null) { + ExecutorUtil.shutdownNowAndAwaitTermination(shardExecutor); } } finally { try { - if (defaultClient != null) { - IOUtils.closeQuietly(defaultClient); + if (loadbalancer != null) { + loadbalancer.close(); } } finally { - ExecutorUtil.shutdownAndAwaitTermination(commExecutor); + try { + if (defaultClient != null) { + IOUtils.closeQuietly(defaultClient); + } + } finally { + ExecutorUtil.shutdownAndAwaitTermination(commExecutor); + } } } IOUtils.closeQuietly(asyncRequestsGauge); @@ -459,6 +479,9 @@ public void initializeMetrics(SolrMetricsContext parentContext, Attributes attri commExecutor = solrMetricsContext.instrumentedExecutorService( commExecutor, "solr.core.executor", "httpShardExecutor", SolrInfoBean.Category.QUERY); + shardExecutor = + solrMetricsContext.instrumentedExecutorService( + shardExecutor, "solr.core.executor", "httpShardVT", SolrInfoBean.Category.QUERY); if (defaultClient != null) { asyncRequestsGauge = solrMetricsContext.observableLongGauge( diff --git a/solr/core/src/java/org/apache/solr/handler/component/ParallelHttpShardHandler.java b/solr/core/src/java/org/apache/solr/handler/component/ParallelHttpShardHandler.java deleted file mode 100644 index 5eb9c992ed93..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/component/ParallelHttpShardHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.solr.handler.component; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutorService; -import net.jcip.annotations.NotThreadSafe; -import org.apache.solr.client.solrj.impl.LBSolrClient; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.params.ModifiableSolrParams; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A version of {@link HttpShardHandler} optimized for massively-sharded collections. - * - *

Uses a {@link HttpShardHandlerFactory#commExecutor} thread for all work related to outgoing - * requests, allowing {@link #submit(ShardRequest, String, ModifiableSolrParams)} to return more - * quickly. (See {@link HttpShardHandler} for comparison.) - * - *

The additional focus on parallelization makes this an ideal implementation for collections - * with many shards. - */ -@NotThreadSafe -public class ParallelHttpShardHandler extends HttpShardHandler { - - @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - private final ExecutorService commExecutor; - - /* - * Unlike the basic HttpShardHandler, this class allows us to exit submit before - * the responseFutureMap is updated. If the runnables that - * do that are slow to execute the calling code could attempt to takeCompleted(), - * while pending is still zero. In this condition, the code would assume that all - * requests are processed (despite the runnables created by this class still - * waiting). Thus, we need to track that there are attempts still in flight. - */ - private final ConcurrentMap> submitFutures; - - public ParallelHttpShardHandler(ParallelHttpShardHandlerFactory httpShardHandlerFactory) { - super(httpShardHandlerFactory); - this.commExecutor = httpShardHandlerFactory.commExecutor; - this.submitFutures = new ConcurrentHashMap<>(); - } - - @Override - protected boolean responsesPending() { - // ensure we can't exit while loop in HttpShardHandler.take(boolean) until we've completed - // submitting all of the shard requests - return super.responsesPending() || !submitFutures.isEmpty(); - } - - @Override - protected void makeShardRequest( - ShardRequest sreq, - String shard, - ModifiableSolrParams params, - LBSolrClient.Req lbReq, - SimpleSolrResponse ssr, - ShardResponse srsp, - long startTimeNS) { - CompletableFuture completableFuture = - CompletableFuture.runAsync( - () -> super.makeShardRequest(sreq, shard, params, lbReq, ssr, srsp, startTimeNS), - commExecutor); - submitFutures.put(srsp, completableFuture); - completableFuture.whenComplete( - (r, t) -> { - try { - if (t != null) { - Throwable failure = t; - if (failure instanceof CompletionException completionException - && completionException.getCause() != null) { - failure = completionException.getCause(); - } - if (!(failure instanceof CancellationException)) { - recordShardSubmitError( - srsp, - new SolrException( - SolrException.ErrorCode.SERVER_ERROR, - "Exception occurred while trying to send a request to shard: " + shard, - failure)); - } - } - } finally { - // Remove so that we keep track of in-flight submits only - submitFutures.remove(srsp); - } - }); - } - - @Override - public void cancelAll() { - super.cancelAll(); - submitFutures - .values() - .forEach( - future -> { - if (!future.isDone()) { - future.cancel(true); - } - }); - submitFutures.clear(); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/component/ParallelHttpShardHandlerFactory.java b/solr/core/src/java/org/apache/solr/handler/component/ParallelHttpShardHandlerFactory.java deleted file mode 100644 index 38b2cb9a974f..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/component/ParallelHttpShardHandlerFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.solr.handler.component; - -/** Creates {@link ParallelHttpShardHandler} instances */ -public class ParallelHttpShardHandlerFactory extends HttpShardHandlerFactory { - - @Override - public ShardHandler getShardHandler() { - return new ParallelHttpShardHandler(this); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/component/ParallelHttpShardHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/component/ParallelHttpShardHandlerTest.java deleted file mode 100644 index 4eb0db8858ff..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/component/ParallelHttpShardHandlerTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.solr.handler.component; - -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.TimeUnit; -import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.client.solrj.impl.LBSolrClient; -import org.apache.solr.client.solrj.request.QueryRequest; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.params.ModifiableSolrParams; -import org.junit.Test; - -public class ParallelHttpShardHandlerTest extends SolrTestCaseJ4 { - - private static class DirectExecutorService extends AbstractExecutorService { - private volatile boolean shutdown; - - @Override - public void shutdown() { - shutdown = true; - } - - @Override - public List shutdownNow() { - shutdown = true; - return List.of(); - } - - @Override - public boolean isShutdown() { - return shutdown; - } - - @Override - public boolean isTerminated() { - return shutdown; - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) { - return shutdown; - } - - @Override - public void execute(Runnable command) { - command.run(); - } - } - - @Test - public void testSubmitFailureIsRecordedWhenSuperThrows() throws Exception { - ParallelHttpShardHandlerFactory factory = new ParallelHttpShardHandlerFactory(); - factory.commExecutor = new DirectExecutorService(); - ParallelHttpShardHandler handler = new ParallelHttpShardHandler(factory); - - // Force super.makeShardRequest to throw before it enqueues the response future. - handler.lbClient = null; - - ShardRequest shardRequest = new ShardRequest(); - shardRequest.params = new ModifiableSolrParams(); - shardRequest.actualShards = new String[] {"shardA"}; - - ShardResponse shardResponse = new ShardResponse(); - shardResponse.setShardRequest(shardRequest); - shardResponse.setShard("shardA"); - - HttpShardHandler.SimpleSolrResponse simpleResponse = new HttpShardHandler.SimpleSolrResponse(); - shardResponse.setSolrResponse(simpleResponse); - - ModifiableSolrParams params = new ModifiableSolrParams(); - QueryRequest queryRequest = new QueryRequest(params); - LBSolrClient.Endpoint endpoint = new LBSolrClient.Endpoint("http://ignored:8983/solr"); - LBSolrClient.Req lbReq = new LBSolrClient.Req(queryRequest, List.of(endpoint)); - - handler.makeShardRequest( - shardRequest, "shardA", params, lbReq, simpleResponse, shardResponse, System.nanoTime()); - - ShardResponse recorded = handler.responses.poll(1, TimeUnit.SECONDS); - - assertNotNull( - "The asynchronous submit should record the shard failure when super.makeShardRequest fails", - recorded); - assertSame( - "The recorded shard response should be the same instance passed into recordShardSubmitError", - shardResponse, - recorded); - assertNotNull( - "Expected an exception to be attached to the recorded shard response", - recorded.getException()); - assertTrue(recorded.getException() instanceof SolrException); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/component/TestShardHandlerFactory.java b/solr/core/src/test/org/apache/solr/handler/component/TestShardHandlerFactory.java index 59325e9b55e9..f7a342230672 100644 --- a/solr/core/src/test/org/apache/solr/handler/component/TestShardHandlerFactory.java +++ b/solr/core/src/test/org/apache/solr/handler/component/TestShardHandlerFactory.java @@ -36,13 +36,11 @@ import org.junit.BeforeClass; import org.junit.Test; -/** Tests exercising Solr's two "out-of-the-box" ShardHandlerFactory implementations */ +/** Tests exercising Solr's "out-of-the-box" {@link HttpShardHandlerFactory} */ public class TestShardHandlerFactory extends SolrTestCaseJ4 { private static final String[] SHARD_HANDLER_FACTORY_IMPLEMENTATIONS = - new String[] { - HttpShardHandlerFactory.class.getName(), ParallelHttpShardHandlerFactory.class.getName() - }; + new String[] {HttpShardHandlerFactory.class.getName()}; private static final String LOAD_BALANCER_REQUESTS_MIN_ABSOLUTE = "solr.tests.loadBalancerRequestsMinimumAbsolute"; diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc index 66e6d21dc7ac..64e478c5fab6 100644 --- a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc +++ b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc @@ -509,9 +509,8 @@ Solr uses "Shard Handlers" to send and track the inter-node requests made intern A factory, configured via the `` element, is used to create new Shard Handlers as needed. The factory defined here will be used throughout Solr, unless overridden by particular requestHandler's in solrconfig.xml. -Two factory implementations are available, each creating a corresponding Shard Handler. -The default, `HttpShardHandlerFactory`, serves as the best option for most deployments. -However some deployments, especially those using authentication or with massively sharded collections, may benefit from the additional parallelization offered by `ParallelHttpShardHandlerFactory`. +Solr ships with a single built-in factory implementation, `HttpShardHandlerFactory`, which dispatches each shard request on its own virtual thread. +This is the default and serves the needs of all deployments, including those with massively sharded collections. Custom shard handlers are also supported and should be referenced in `solr.xml` by their fully-qualified class name: @@ -520,7 +519,7 @@ Custom shard handlers are also supported and should be referenced in `solr.xml` ---- -Sub-elements of `` may vary in the case of custom shard handlers, but both `HttpShardHandlerFactory` and `ParallelShardHandlerFactory` support the following configuration options: +Sub-elements of `` may vary in the case of custom shard handlers, but `HttpShardHandlerFactory` supports the following configuration options: [[sockettimeout]]`socketTimeout`:: + diff --git a/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java b/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java index 48ac61f55d87..0ca441671561 100644 --- a/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java +++ b/solr/solrj/src/java/org/apache/solr/common/util/ExecutorUtil.java @@ -65,6 +65,11 @@ public static synchronized void addThreadLocalProvider(InheritableThreadLocalPro providers = copy; } + /** Snapshot of currently-registered {@link InheritableThreadLocalProvider}s. */ + public static List getThreadLocalProviders() { + return providers; + } + /** * Any class which wants to carry forward the thread local values to the threads run by thread * pools must implement this interface and the implementation should be registered here