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

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

commit 7ee47d338e8fe0df9befc265f5a7aedc45c8d698
Author: David Smiley <[email protected]>
AuthorDate: Fri Nov 28 10:55:46 2025 -0500

    SOLR-17161: LBHttp2SolrClient is now LBAsyncSolrClient (#3882)
    
    * renamed LBHttp2SolrClient to LBAsyncSolrClient because it's code is 
mostly for requestAsync.
    * LBAsyncSolrClient is abstract
    * new LBJettySolrClient extends LBAsyncSolrClient; only used by 
HttpShardHandler
    * LBSolrClient can now be used directly easily, albeit technically remains 
abstract; took the builder code formerly in LBHttp2SolrClient
    * CloudHttp2SolrClient internally now only needs an LBSolrClient; not 
LBHttp2SolrClient (no requestAsync needed).
    
    SOLR-17161 (a first part of this issue, adds a "jetty" package)
---
 .../unreleased/SOLR-17161-LBAsyncSolrClient.yml    |   8 ++
 .../solr/handler/component/HttpShardHandler.java   |   7 +-
 .../handler/component/HttpShardHandlerFactory.java |   7 +-
 .../modules/deployment-guide/pages/solrj.adoc      |   4 +-
 .../client/solrj/impl/CloudHttp2SolrClient.java    |  12 +-
 .../solr/client/solrj/impl/Http2SolrClient.java    |   6 +
 .../solr/client/solrj/impl/HttpJdkSolrClient.java  |   5 +
 .../solr/client/solrj/impl/HttpSolrClientBase.java |   5 +
 ...Http2SolrClient.java => LBAsyncSolrClient.java} | 143 +++------------------
 .../solr/client/solrj/impl/LBSolrClient.java       | 113 +++++++++++++++-
 .../solr/client/solrj/jetty/LBJettySolrClient.java |  54 ++++++++
 .../solr/client/solrj/jetty/package-info.java      |  19 +++
 .../impl/CloudHttp2SolrClientBuilderTest.java      |   2 +-
 ...IntegrationTest.java => LB2SolrClientTest.java} |  19 +--
 ...rClientTest.java => LBAsyncSolrClientTest.java} |  53 ++------
 .../solr/client/solrj/apache/LBHttpSolrClient.java |   3 +-
 16 files changed, 260 insertions(+), 200 deletions(-)

diff --git a/changelog/unreleased/SOLR-17161-LBAsyncSolrClient.yml 
b/changelog/unreleased/SOLR-17161-LBAsyncSolrClient.yml
new file mode 100644
index 00000000000..f722b2ffb46
--- /dev/null
+++ b/changelog/unreleased/SOLR-17161-LBAsyncSolrClient.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: LBAsyncSolrClient (renamed from LBHttp2SolrClient) & LBSolrClient 
(improved) & LBJettySolrClient (new, clarifying impl).
+type: other # added, changed, fixed, deprecated, removed, dependency_update, 
security, other
+authors:
+  - name: David Smiley
+links:
+  - name: SOLR-17161
+    url: https://issues.apache.org/jira/browse/SOLR-17161
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 b58da55859a..788d985a79e 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
@@ -33,8 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import net.jcip.annotations.NotThreadSafe;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
-import org.apache.solr.client.solrj.impl.Http2SolrClient;
-import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
+import org.apache.solr.client.solrj.impl.LBAsyncSolrClient;
 import org.apache.solr.client.solrj.impl.LBSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.routing.NoOpReplicaListTransformer;
@@ -98,7 +97,7 @@ public class HttpShardHandler extends ShardHandler {
   private final AtomicBoolean canceled = new AtomicBoolean(false);
 
   private final Map<String, List<String>> shardToURLs;
-  protected LBHttp2SolrClient<Http2SolrClient> lbClient;
+  protected LBAsyncSolrClient lbClient;
 
   public HttpShardHandler(HttpShardHandlerFactory httpShardHandlerFactory) {
     this.httpShardHandlerFactory = httpShardHandlerFactory;
@@ -247,7 +246,7 @@ public class HttpShardHandler extends ShardHandler {
    * @param sreq the request to make
    * @param shard the shard to address
    * @param params request parameters
-   * @param lbReq the load balanced request suitable for LBHttp2SolrClient
+   * @param lbReq the load balanced request
    * @param ssr the response collector part 1
    * @param srsp the shard response collector
    * @param startTimeNS the time at which the request was initiated, likely 
just prior to calling
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 0a78e179019..0daec7a4825 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
@@ -32,9 +32,10 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.impl.Http2SolrClient;
-import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
+import org.apache.solr.client.solrj.impl.LBAsyncSolrClient;
 import org.apache.solr.client.solrj.impl.LBSolrClient;
 import org.apache.solr.client.solrj.impl.SolrHttpConstants;
+import org.apache.solr.client.solrj.jetty.LBJettySolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import 
org.apache.solr.client.solrj.routing.AffinityReplicaListTransformerFactory;
 import org.apache.solr.client.solrj.routing.ReplicaListTransformer;
@@ -82,7 +83,7 @@ public class HttpShardHandlerFactory extends 
ShardHandlerFactory
 
   protected volatile Http2SolrClient defaultClient;
   protected InstrumentedHttpListenerFactory httpListenerFactory;
-  protected LBHttp2SolrClient<Http2SolrClient> loadbalancer;
+  protected LBAsyncSolrClient loadbalancer;
 
   int corePoolSize = 0;
   int maximumPoolSize = Integer.MAX_VALUE;
@@ -307,7 +308,7 @@ public class HttpShardHandlerFactory extends 
ShardHandlerFactory
             .withMaxConnectionsPerHost(maxConnectionsPerHost)
             .build();
     this.defaultClient.addListenerFactory(this.httpListenerFactory);
-    this.loadbalancer = new 
LBHttp2SolrClient.Builder<Http2SolrClient>(defaultClient).build();
+    this.loadbalancer = new LBJettySolrClient.Builder(defaultClient).build();
 
     initReplicaListTransformers(getParameter(args, "replicaRouting", null, 
sb));
 
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/solrj.adoc 
b/solr/solr-ref-guide/modules/deployment-guide/pages/solrj.adoc
index e61f17567f7..5582621ec7b 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/solrj.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/solrj.adoc
@@ -97,7 +97,7 @@ Requests are sent in the form of 
{solr-javadocs}/solrj/org/apache/solr/client/so
 
 - 
{solr-javadocs}/solrj/org/apache/solr/client/solrj/impl/Http2SolrClient.html[`Http2SolrClient`]
 - a general purpose client based on Jetty HttpClient.  Supports HTTP/2 and 
HTTP/1.1, async, non-blocking.  Most used & tested.
 - 
{solr-javadocs}/solrj/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.html[`HttpJdkSolrClient`]
 - a general purpose client based on JDK HttpClient.  Supports HTTP/2 and 
HTTP/1.1, async, non-blocking.  Has no dependencies.
-- 
{solr-javadocs}/solrj/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.html[`LBHttp2SolrClient`]
 - an internal client that delegates to other clients pointed at different URLs 
for fail-over/availability.  Adjusts the list of "in-service" nodes based on 
node health.
+- 
{solr-javadocs}/solrj/org/apache/solr/client/solrj/impl/LBSolrClient.html[`LBSolrClient`]
 - an internal client that delegates to other clients pointed at different URLs 
for fail-over/availability.  Adjusts the list of "in-service" nodes based on 
node health.
 - 
{solr-javadocs}/solrj/org/apache/solr/client/solrj/impl/CloudSolrClient.html[`CloudSolrClient`]
 - the ideal client for SolrCloud.  Using the "cluster state", it routes 
requests to the optimal nodes, including splitting out the documents in an 
UpdateRequest to different nodes.
 - 
{solr-javadocs}/solrj/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClient.html[`ConcurrentUpdateHttp2SolrClient`]
 - geared towards indexing-centric workloads.
 Buffers documents internally before sending larger batches to Solr.
@@ -116,7 +116,7 @@ A few notable exceptions to this are described below:
 
 - *Http2SolrClient* - Users of `Http2SolrClient` may choose to skip providing 
a root URL to their client, in favor of specifying the URL as an argument for 
the `Http2SolrClient.requestWithBaseUrl` method.
 Calling any other `request` methods on a URL-less `Http2SolrClient` will 
result in an `IllegalArgumentException`.
-- *LBHttp2SolrClient* - Solr's "load balancing" client is frequently used to 
round-robin requests across a set of replicas or cores.
+- *LBSolrClient* - Solr's "load balancing" client is frequently used to 
round-robin requests across a set of replicas or cores.
 URLs are still expected to point to the Solr root (i.e. "/solr"), but to 
support this use-case the URLs are often supplemented by an additional 
parameter to specify the targeted core.
 Alternatively, some "load balancing" methods make use of an `Endpoint` 
abstraction to provide this URL and core information in a more structured way.
 - *CloudSolrClient* - Like many clients, CloudSolrClient accepts a series of 
URLs pointing to the Solr root path (i.e. "/solr").
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
index 4fc82b630d3..6886952c25c 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java
@@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
 /**
  * SolrJ client class to communicate with SolrCloud using an Http/2-capable 
Solr Client. Instances
  * of this class communicate with Zookeeper to discover Solr endpoints for 
SolrCloud collections,
- * and then use the {@link LBHttp2SolrClient} to issue requests.
+ * and then use the {@link LBSolrClient} to issue requests.
  *
  * @since solr 8.0
  */
@@ -44,7 +44,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   private final ClusterStateProvider stateProvider;
-  private final LBHttp2SolrClient<HttpSolrClientBase> lbClient;
+  private final LBSolrClient lbClient;
   private final HttpSolrClientBase myClient;
   private final boolean clientIsInternal;
 
@@ -90,7 +90,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
     // locks.
     this.locks = objectList(builder.parallelCacheRefreshesLocks);
 
-    this.lbClient = new LBHttp2SolrClient.Builder<>(myClient).build();
+    this.lbClient = builder.createOrGetLbClient(myClient);
   }
 
   private HttpSolrClientBase createOrGetHttpClientFromBuilder(Builder builder) 
{
@@ -160,7 +160,7 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
   }
 
   @Override
-  public LBHttp2SolrClient<?> getLbClient() {
+  public LBSolrClient getLbClient() {
     return lbClient;
   }
 
@@ -452,5 +452,9 @@ public class CloudHttp2SolrClient extends CloudSolrClient {
 
       return new CloudHttp2SolrClient(this);
     }
+
+    protected LBSolrClient createOrGetLbClient(HttpSolrClientBase myClient) {
+      return myClient.createLBSolrClient();
+    }
   }
 }
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
index 83b24671bbe..d8005979e90 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
@@ -47,6 +47,7 @@ import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.embedded.SSLConfig;
 import 
org.apache.solr.client.solrj.impl.HttpListenerFactory.RequestResponseListener;
+import org.apache.solr.client.solrj.jetty.LBJettySolrClient;
 import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.util.ClientUtils;
@@ -638,6 +639,11 @@ public class Http2SolrClient extends HttpSolrClientBase {
     }
   }
 
+  @Override
+  protected LBSolrClient createLBSolrClient() {
+    return new LBJettySolrClient.Builder(this).build();
+  }
+
   @Override
   public HttpSolrClientBuilderBase<?, ?> builder() {
     return new Http2SolrClient.Builder().withHttpClient(this);
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
index d65a8fb9681..4e0472cc2d8 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
@@ -554,6 +554,11 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
     return new HttpJdkSolrClient.Builder().withHttpClient(this);
   }
 
+  @Override
+  protected LBSolrClient createLBSolrClient() {
+    return new LBSolrClient.Builder<>(this).build();
+  }
+
   public static class Builder
       extends HttpSolrClientBuilderBase<HttpJdkSolrClient.Builder, 
HttpJdkSolrClient> {
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
index 0fa2936cbfc..ffed7779fb4 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
@@ -101,6 +101,11 @@ public abstract class HttpSolrClientBase extends 
SolrClient {
 
   public abstract HttpSolrClientBuilderBase<?, ?> builder();
 
+  /**
+   * @lucene.internal
+   */
+  protected abstract LBSolrClient createLBSolrClient();
+
   protected String getRequestUrl(SolrRequest<?> solrRequest, String collection)
       throws MalformedURLException {
     return ClientUtils.buildRequestUrl(solrRequest, serverBaseUrl, collection);
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBAsyncSolrClient.java
similarity index 58%
rename from 
solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java
rename to 
solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBAsyncSolrClient.java
index 89ffbd707a0..41225ffaf25 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBHttp2SolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBAsyncSolrClient.java
@@ -20,109 +20,32 @@ import java.io.IOException;
 import java.net.ConnectException;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
-import java.util.Arrays;
-import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
-import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.slf4j.MDC;
 
-/**
- * This "LoadBalanced Http Solr Client" is a load balancing wrapper around a 
Http Solr Client. This
- * is useful when you have multiple Solr endpoints and requests need to be 
Load Balanced among them.
- *
- * <p>Do <b>NOT</b> use this class for indexing in leader/follower scenarios 
since documents must be
- * sent to the correct leader; no inter-node routing is done.
- *
- * <p>In SolrCloud (leader/replica) scenarios, it is usually better to use 
{@link CloudSolrClient},
- * but this class may be used for updates because the server will forward them 
to the appropriate
- * leader.
- *
- * <p>It offers automatic failover when a server goes down, and it detects 
when the server comes
- * back up.
- *
- * <p>Load balancing is done using a simple round-robin on the list of 
endpoints. Endpoint URLs are
- * expected to point to the Solr "root" path (i.e. "/solr").
- *
- * <blockquote>
- *
- * <pre>
- * SolrClient client = new LBHttp2SolrClient.Builder(http2SolrClient,
- *         new LBSolrClient.Endpoint("http://host1:8080/solr";), new 
LBSolrClient.Endpoint("http://host2:8080/solr";))
- *     .build();
- * </pre>
- *
- * </blockquote>
- *
- * Users who wish to balance traffic across a specific set of replicas or 
cores may specify each
- * endpoint as a root-URL and core-name pair. For example:
- *
- * <blockquote>
- *
- * <pre>
- * SolrClient client = new LBHttp2SolrClient.Builder(http2SolrClient,
- *         new LBSolrClient.Endpoint("http://host1:8080/solr";, "coreA"),
- *         new LBSolrClient.Endpoint("http://host2:8080/solr";, "coreB"))
- *     .build();
- * </pre>
- *
- * </blockquote>
- *
- * <p>If a request to an endpoint fails by an IOException due to a connection 
timeout or read
- * timeout then the host is taken off the list of live endpoints and moved to 
a 'dead endpoint list'
- * and the request is resent to the next live endpoint. This process is 
continued till it tries all
- * the live endpoints. If at least one endpoint is alive, the request 
succeeds, and if not it fails.
- *
- * <p>Dead endpoints are periodically healthchecked on a fixed interval 
controlled by {@link
- * LBHttp2SolrClient.Builder#setAliveCheckInterval(int, TimeUnit)}. The 
default is set to one
- * minute.
- *
- * <p><b>When to use this?</b><br>
- * This can be used as a software load balancer when you do not wish to set up 
an external load
- * balancer. Alternatives to this code are to use a dedicated hardware load 
balancer or using Apache
- * httpd with mod_proxy_balancer as a load balancer. See <a
- * href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load 
balancing on Wikipedia</a>
- *
- * @since solr 8.0
- */
-public class LBHttp2SolrClient<C extends HttpSolrClientBase> extends 
LBSolrClient {
+/** A {@link LBSolrClient} adding {@link #requestAsync(Req)}. */
+public abstract class LBAsyncSolrClient extends LBSolrClient {
+  // formerly known as LBHttp2SolrClient, using Http2SolrClient (jetty)
 
-  protected final C solrClient;
+  protected final HttpSolrClientBase solrClient;
 
-  @SuppressWarnings("unchecked")
-  private LBHttp2SolrClient(Builder<?> builder) {
-    super(Arrays.asList(builder.solrEndpoints));
-    this.solrClient = (C) builder.solrClient;
-    this.aliveCheckIntervalMillis = builder.aliveCheckIntervalMillis;
-    this.defaultCollection = builder.defaultCollection;
+  protected LBAsyncSolrClient(Builder<?> builder) {
+    super(builder);
+    this.solrClient = builder.getSolrClient();
   }
 
   @Override
-  protected SolrClient getClient(Endpoint endpoint) {
+  protected HttpSolrClientBase getClient(Endpoint endpoint) {
     return solrClient;
   }
 
-  @Override
-  public ResponseParser getParser() {
-    return solrClient.getParser();
-  }
-
-  @Override
-  public RequestWriter getRequestWriter() {
-    return solrClient.getRequestWriter();
-  }
-
-  public Set<String> getUrlParamNames() {
-    return solrClient.getUrlParamNames();
-  }
-
   /**
    * Execute an asynchronous request against one or more hosts for a given 
collection. The passed-in
    * Req object includes a List of Endpoints. This method always begins with 
the first Endpoint in
@@ -214,10 +137,9 @@ public class LBHttp2SolrClient<C extends 
HttpSolrClientBase> extends LBSolrClien
       RetryListener listener) {
     String baseUrl = endpoint.toString();
     rsp.server = baseUrl;
-    final var client = (Http2SolrClient) getClient(endpoint);
     try {
       CompletableFuture<NamedList<Object>> future =
-          client.requestWithBaseUrl(baseUrl, (c) -> 
c.requestAsync(req.getRequest()));
+          requestAsyncWithUrl(getClient(endpoint), baseUrl, req.getRequest());
       future.whenComplete(
           (result, throwable) -> {
             if (!future.isCompletedExceptionally()) {
@@ -228,11 +150,15 @@ public class LBHttp2SolrClient<C extends 
HttpSolrClientBase> extends LBSolrClien
           });
       return future;
     } catch (SolrServerException | IOException e) {
-      // Unreachable, since 'requestWithBaseUrl' above is running the request 
asynchronously
+      // Unreachable, since 'requestAsyncWithUrl' above is running the request 
asynchronously
       throw new RuntimeException(e);
     }
   }
 
+  protected abstract CompletableFuture<NamedList<Object>> requestAsyncWithUrl(
+      SolrClient client, String baseUrl, SolrRequest<?> request)
+      throws SolrServerException, IOException;
+
   private void onSuccessfulRequest(
       NamedList<Object> result,
       Endpoint endpoint,
@@ -293,43 +219,4 @@ public class LBHttp2SolrClient<C extends 
HttpSolrClientBase> extends LBSolrClien
       listener.onFailure(new SolrServerException(e), false);
     }
   }
-
-  public static class Builder<C extends HttpSolrClientBase> {
-
-    private final C solrClient;
-    private final LBSolrClient.Endpoint[] solrEndpoints;
-    private long aliveCheckIntervalMillis =
-        TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); // 1 minute 
between checks
-    protected String defaultCollection;
-
-    public Builder(C solrClient, Endpoint... endpoints) {
-      this.solrClient = solrClient;
-      this.solrEndpoints = endpoints;
-    }
-
-    /**
-     * LBHttpSolrServer keeps pinging the dead servers at fixed interval to 
find if it is alive. Use
-     * this to set that interval
-     *
-     * @param aliveCheckInterval how often to ping for aliveness
-     */
-    public Builder<C> setAliveCheckInterval(int aliveCheckInterval, TimeUnit 
unit) {
-      if (aliveCheckInterval <= 0) {
-        throw new IllegalArgumentException(
-            "Alive check interval must be " + "positive, specified value = " + 
aliveCheckInterval);
-      }
-      this.aliveCheckIntervalMillis = 
TimeUnit.MILLISECONDS.convert(aliveCheckInterval, unit);
-      return this;
-    }
-
-    /** Sets a default for core or collection based requests. */
-    public Builder<C> withDefaultCollection(String defaultCoreOrCollection) {
-      this.defaultCollection = defaultCoreOrCollection;
-      return this;
-    }
-
-    public LBHttp2SolrClient<C> build() {
-      return new LBHttp2SolrClient<C>(this);
-    }
-  }
 }
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java
index c71d0210bec..1b9fbc5a3c6 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/LBSolrClient.java
@@ -61,6 +61,61 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
+/**
+ * This "LoadBalanced Http Solr Client" is a load balancing wrapper around an 
Http Solr Client. This
+ * is useful when you have multiple Solr endpoints and requests need to be 
Load Balanced among them.
+ *
+ * <p>Do <b>NOT</b> use this class for indexing in leader/follower scenarios 
since documents must be
+ * sent to the correct leader; no inter-node routing is done.
+ *
+ * <p>In SolrCloud (leader/replica) scenarios, it is usually better to use 
{@link CloudSolrClient},
+ * but this class may be used for updates because the server will forward them 
to the appropriate
+ * leader.
+ *
+ * <p>It offers automatic failover when a server goes down, and it detects 
when the server comes
+ * back up.
+ *
+ * <p>Load balancing is done using a simple round-robin on the list of 
endpoints. Endpoint URLs are
+ * expected to point to the Solr "root" path (i.e. "/solr").
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * SolrClient client = new LBSolrClient.Builder(http2SolrClient,
+ *         new LBSolrClient.Endpoint("http://host1:8080/solr";), new 
LBSolrClient.Endpoint("http://host2:8080/solr";))
+ *     .build();
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * Users who wish to balance traffic across a specific set of replicas or 
cores may specify each
+ * endpoint as a root-URL and core-name pair. For example:
+ *
+ * <blockquote>
+ *
+ * <pre>
+ * SolrClient client = new LBSolrClient.Builder(http2SolrClient,
+ *         new LBSolrClient.Endpoint("http://host1:8080/solr";, "coreA"),
+ *         new LBSolrClient.Endpoint("http://host2:8080/solr";, "coreB"))
+ *     .build();
+ * </pre>
+ *
+ * </blockquote>
+ *
+ * <p>If a request to an endpoint fails by an IOException due to a connection 
timeout or read
+ * timeout then the host is taken off the list of live endpoints and moved to 
a 'dead endpoint list'
+ * and the request is resent to the next live endpoint. This process is 
continued till it tries all
+ * the live endpoints. If at least one endpoint is alive, the request 
succeeds, and if not it fails.
+ *
+ * <p>Dead endpoints are periodically healthchecked on a fixed interval 
controlled by {@code
+ * LBSolrClient.Builder#setAliveCheckInterval(int, TimeUnit)}. The default is 
set to one minute.
+ *
+ * <p><b>When to use this?</b><br>
+ * This can be used as a software load balancer when you do not wish to set up 
an external load
+ * balancer. Alternatives to this code are to use a dedicated hardware load 
balancer or using Apache
+ * httpd with mod_proxy_balancer as a load balancer. See <a
+ * href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load 
balancing on Wikipedia</a>
+ */
 public abstract class LBSolrClient extends SolrClient {
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -110,6 +165,54 @@ public abstract class LBSolrClient extends SolrClient {
     solrQuery.setDistrib(false);
   }
 
+  public static class Builder<C extends HttpSolrClientBase> {
+
+    private final C solrClient;
+    private final Endpoint[] solrEndpoints;
+    private long aliveCheckIntervalMillis =
+        TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); // 1 minute 
between checks
+    protected String defaultCollection;
+
+    public Builder(C solrClient, Endpoint... endpoints) {
+      this.solrClient = solrClient;
+      this.solrEndpoints = endpoints;
+    }
+
+    /**
+     * LBHttpSolrServer keeps pinging the dead servers at fixed interval to 
find if it is alive. Use
+     * this to set that interval
+     *
+     * @param aliveCheckInterval how often to ping for aliveness
+     */
+    public Builder<C> setAliveCheckInterval(int aliveCheckInterval, TimeUnit 
unit) {
+      if (aliveCheckInterval <= 0) {
+        throw new IllegalArgumentException(
+            "Alive check interval must be " + "positive, specified value = " + 
aliveCheckInterval);
+      }
+      this.aliveCheckIntervalMillis = 
TimeUnit.MILLISECONDS.convert(aliveCheckInterval, unit);
+      return this;
+    }
+
+    /** Sets a default for core or collection based requests. */
+    public Builder<C> withDefaultCollection(String defaultCoreOrCollection) {
+      this.defaultCollection = defaultCoreOrCollection;
+      return this;
+    }
+
+    public C getSolrClient() {
+      return solrClient;
+    }
+
+    public LBSolrClient build() {
+      return new LBSolrClient(this) {
+        @Override
+        protected SolrClient getClient(Endpoint endpoint) {
+          return solrClient;
+        }
+      };
+    }
+  }
+
   /**
    * A Solr endpoint for {@link LBSolrClient} to include in its load-balancing
    *
@@ -401,7 +504,15 @@ public abstract class LBSolrClient extends SolrClient {
     }
   }
 
-  public LBSolrClient(List<Endpoint> solrEndpoints) {
+  protected LBSolrClient(Builder<?> builder) {
+    this(Arrays.asList(builder.solrEndpoints));
+    this.aliveCheckIntervalMillis = builder.aliveCheckIntervalMillis;
+    this.defaultCollection = builder.defaultCollection;
+    this.requestWriter = builder.solrClient.getRequestWriter();
+    this.parser = builder.solrClient.getParser();
+  }
+
+  protected LBSolrClient(List<Endpoint> solrEndpoints) {
     if (!solrEndpoints.isEmpty()) {
       for (Endpoint s : solrEndpoints) {
         EndpointWrapper wrapper = createServerWrapper(s);
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/jetty/LBJettySolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/jetty/LBJettySolrClient.java
new file mode 100644
index 00000000000..4790fabcf3a
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/jetty/LBJettySolrClient.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.solrj.jetty;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.impl.LBAsyncSolrClient;
+import org.apache.solr.client.solrj.impl.LBSolrClient;
+import org.apache.solr.common.util.NamedList;
+
+/** An {@link LBSolrClient} based on Jetty HttpClient, supporting async. */
+public class LBJettySolrClient extends LBAsyncSolrClient {
+
+  protected LBJettySolrClient(Builder builder) {
+    super(builder);
+  }
+
+  public static class Builder extends LBSolrClient.Builder<Http2SolrClient> {
+
+    public Builder(Http2SolrClient solrClient, Endpoint... endpoints) {
+      super(solrClient, endpoints);
+    }
+
+    @Override
+    public LBJettySolrClient build() {
+      return new LBJettySolrClient(this);
+    }
+  }
+
+  @Override
+  protected CompletableFuture<NamedList<Object>> requestAsyncWithUrl(
+      SolrClient client, String baseUrl, SolrRequest<?> request)
+      throws SolrServerException, IOException {
+    return ((Http2SolrClient) client).requestWithBaseUrl(baseUrl, c -> 
c.requestAsync(request));
+  }
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/jetty/package-info.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/jetty/package-info.java
new file mode 100644
index 00000000000..cd7607f2790
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/jetty/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** SolrJ code that depends on Jetty client / HttpClient. */
+package org.apache.solr.client.solrj.jetty;
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBuilderTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBuilderTest.java
index e4292ee3185..6f8f8587b51 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBuilderTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBuilderTest.java
@@ -214,7 +214,7 @@ public class CloudHttp2SolrClientBuilderTest extends 
SolrCloudTestCase {
                 Collections.singletonList(ANY_ZK_HOST), 
Optional.of(ANY_CHROOT))
             .build()) {
       assertTrue(createdClient.getHttpClient() instanceof Http2SolrClient);
-      assertTrue(createdClient.getLbClient().solrClient instanceof 
Http2SolrClient);
+      assertTrue(createdClient.getLbClient().getClient(null) instanceof 
Http2SolrClient);
     }
   }
 
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
 b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LB2SolrClientTest.java
similarity index 94%
rename from 
solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
rename to 
solr/solrj/src/test/org/apache/solr/client/solrj/impl/LB2SolrClientTest.java
index 3f24984f90e..ed0975b5181 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientIntegrationTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LB2SolrClientTest.java
@@ -18,7 +18,6 @@ package org.apache.solr.client.solrj.impl;
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.lang.invoke.MethodHandles;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -41,18 +40,14 @@ import org.apache.solr.util.LogLevel;
 import org.apache.solr.util.LogListener;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * Test for LBHttp2SolrClient
+ * Integration test for {@link LBSolrClient}
  *
  * @since solr 1.4
  */
 @LogLevel("org.apache.solr.client.solrj.impl=DEBUG")
-public class LBHttp2SolrClientIntegrationTest extends SolrTestCaseJ4 {
-
-  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+public class LB2SolrClientTest extends SolrTestCaseJ4 {
 
   SolrInstance[] solr = new SolrInstance[3];
 
@@ -124,7 +119,7 @@ public class LBHttp2SolrClientIntegrationTest extends 
SolrTestCaseJ4 {
               .withIdleTimeout(2000, TimeUnit.MILLISECONDS)
               .build();
       var lbClient =
-          new LBHttp2SolrClient.Builder<>(delegateClient, baseSolrEndpoints)
+          new LBSolrClient.Builder<>(delegateClient, baseSolrEndpoints)
               .withDefaultCollection(solr[0].getDefaultCollection())
               .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
               .build();
@@ -137,7 +132,7 @@ public class LBHttp2SolrClientIntegrationTest extends 
SolrTestCaseJ4 {
               .withSSLContext(MockTrustManager.ALL_TRUSTING_SSL_CONTEXT)
               .build();
       var lbClient =
-          new LBHttp2SolrClient.Builder<>(delegateClient, baseSolrEndpoints)
+          new LBSolrClient.Builder<>(delegateClient, baseSolrEndpoints)
               .withDefaultCollection(solr[0].getDefaultCollection())
               .setAliveCheckInterval(500, TimeUnit.MILLISECONDS)
               .build();
@@ -316,10 +311,10 @@ public class LBHttp2SolrClientIntegrationTest extends 
SolrTestCaseJ4 {
 
   private static class LBClientHolder implements AutoCloseable {
 
-    final LBHttp2SolrClient<?> lbClient;
-    final HttpSolrClientBase delegate;
+    final LBSolrClient lbClient;
+    final SolrClient delegate; // http
 
-    LBClientHolder(LBHttp2SolrClient<?> lbClient, HttpSolrClientBase delegate) 
{
+    LBClientHolder(LBSolrClient lbClient, SolrClient delegate) {
       this.lbClient = lbClient;
       this.delegate = delegate;
     }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBAsyncSolrClientTest.java
similarity index 87%
rename from 
solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
rename to 
solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBAsyncSolrClientTest.java
index 9d2019309b0..a83bd793b49 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttp2SolrClientTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBAsyncSolrClientTest.java
@@ -19,10 +19,8 @@ package org.apache.solr.client.solrj.impl;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -31,42 +29,15 @@ import org.apache.solr.SolrTestCase;
 import org.apache.solr.client.solrj.SolrClientFunction;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.jetty.LBJettySolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.junit.Test;
 
-/** Test the LBHttp2SolrClient. */
-public class LBHttp2SolrClientTest extends SolrTestCase {
-
-  /**
-   * Test method for {@link LBHttp2SolrClient.Builder} that validates that the 
query param keys
-   * passed in by the base <code>Http2SolrClient
-   * </code> instance are used by the LBHttp2SolrClient.
-   */
-  @Test
-  public void testLBHttp2SolrClientWithTheseParamNamesInTheUrl() {
-    String url = "http://127.0.0.1:8080";;
-    Set<String> urlParamNames = new HashSet<>(2);
-    urlParamNames.add("param1");
-
-    try (Http2SolrClient http2SolrClient =
-            new 
Http2SolrClient.Builder(url).withTheseParamNamesInTheUrl(urlParamNames).build();
-        LBHttp2SolrClient<Http2SolrClient> testClient =
-            new LBHttp2SolrClient.Builder<>(http2SolrClient, new 
LBSolrClient.Endpoint(url))
-                .build()) {
-
-      assertArrayEquals(
-          "Wrong urlParamNames found in lb client.",
-          urlParamNames.toArray(),
-          testClient.getUrlParamNames().toArray());
-      assertArrayEquals(
-          "Wrong urlParamNames found in base client.",
-          urlParamNames.toArray(),
-          http2SolrClient.getUrlParamNames().toArray());
-    }
-  }
+/** Test the LBAsyncSolrClient. */
+public class LBAsyncSolrClientTest extends SolrTestCase {
 
   @Test
   public void testSynchronous() throws Exception {
@@ -76,10 +47,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
 
     Http2SolrClient.Builder b =
         new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
-    ;
+
     try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
-        LBHttp2SolrClient<MockHttpSolrClient> testClient =
-            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+        var testClient = new LBJettySolrClient.Builder(client, ep1, 
ep2).build()) {
 
       String lastEndpoint = null;
       for (int i = 0; i < 10; i++) {
@@ -105,10 +75,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
 
     Http2SolrClient.Builder b =
         new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
-    ;
+
     try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
-        LBHttp2SolrClient<MockHttpSolrClient> testClient =
-            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+        var testClient = new LBJettySolrClient.Builder(client, ep1, 
ep2).build()) {
 
       client.basePathToFail = ep1.getBaseUrl();
       String basePathToSucceed = ep2.getBaseUrl();
@@ -164,10 +133,9 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
 
     Http2SolrClient.Builder b =
         new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
-    ;
+
     try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
-        LBHttp2SolrClient<MockHttpSolrClient> testClient =
-            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+        var testClient = new LBJettySolrClient.Builder(client, ep1, 
ep2).build()) {
 
       for (int j = 0; j < 2; j++) {
         // first time Endpoint One will return error code 500.
@@ -230,8 +198,7 @@ public class LBHttp2SolrClientTest extends SolrTestCase {
     Http2SolrClient.Builder b =
         new 
Http2SolrClient.Builder("http://base.url";).withConnectionTimeout(10, 
TimeUnit.SECONDS);
     try (MockHttpSolrClient client = new MockHttpSolrClient("http://base.url";, 
b);
-        LBHttp2SolrClient<MockHttpSolrClient> testClient =
-            new LBHttp2SolrClient.Builder<>(client, ep1, ep2).build()) {
+        var testClient = new LBJettySolrClient.Builder(client, ep1, 
ep2).build()) {
 
       int limit = 10; // For simplicity use an even limit
       List<CompletableFuture<LBSolrClient.Rsp>> responses = new ArrayList<>();
diff --git 
a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
 
b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
index b926808ddb9..bac755dc692 100644
--- 
a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
+++ 
b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
@@ -26,7 +26,6 @@ import org.apache.http.client.HttpClient;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.JavaBinResponseParser;
-import org.apache.solr.client.solrj.impl.LBHttp2SolrClient;
 import org.apache.solr.client.solrj.impl.LBSolrClient;
 import org.apache.solr.common.params.ModifiableSolrParams;
 
@@ -95,7 +94,7 @@ import org.apache.solr.common.params.ModifiableSolrParams;
  * href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load 
balancing on Wikipedia</a>
  *
  * @since solr 1.4
- * @deprecated Please use {@link LBHttp2SolrClient}
+ * @deprecated Not supporting Apache HttpClient based implementations anymore
  */
 @Deprecated(since = "9.0")
 public class LBHttpSolrClient extends LBSolrClient {


Reply via email to