This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 8c49326e71b SOLR-18188: Misc test fixes (#4496)
8c49326e71b is described below
commit 8c49326e71b2c18388b25c8c77ff3fbdda666be8
Author: David Smiley <[email protected]>
AuthorDate: Fri Jun 5 16:01:54 2026 -0400
SOLR-18188: Misc test fixes (#4496)
Accumulated from several commits on the misc-test-fixes branch.
PrepRecoveryOp: add class-level javadoc to explain its purpose.
ShowFileRequestHandlerTest, TestPrometheusResponseWriter,
TestCborDataFormat: close response streams to fix resource leaks
flagged by the test framework.
TestDistributedTracing: add retry logic around span assertions to
reduce flakiness caused by timing-sensitive telemetry delivery.
SocketProxy: call setSoTimeout(1000) on the server socket so the
accept loop unblocks promptly when close() is called. Without a
timeout the acceptor thread blocks indefinitely in accept() and
leaks, causing test hangs or "thread leaked" warnings.
JettySolrRunner: move proxy.close() to before server.stop() rather
than leaving it in the finally block. If qtp.join() hangs (e.g. stuck
PrepRecoveryOp handlers or H2C session threads) the finally block is
never reached, leaving the SocketProxy acceptor thread leaked. Closing
the proxy first ensures cleanup even when server shutdown hangs.
HttpJettySolrClientCompatibilityTest: switch from @ClassRule to @Rule
so the Jetty server lifecycle is per-test as intended.
TestSolrJ: delete this file -- it contains no actual tests.
ClientUtils: add Objects.requireNonNull() guard on serverRootUrl to
surface null earlier with a clear error rather than a cryptic NPE
downstream.
AbstractFullDistribZkTestBase: wrap control-collection creation in
RetryUtil.retryOnException() to tolerate transient ZooKeeper errors
during setup. Also move coreClients.clear(),
solrClientByCollection.clear(), and super.destroyServers() into a
finally block after ExecutorUtil.shutdownAndAwaitTermination() so
teardown always completes even if the executor shutdown throws.
HttpPartitionTest: decrease leaderConflictResolveWait to 10sec so
stale PrepRecoveryOp handlers time out within the Jetty graceful stop window
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
.../apache/solr/handler/admin/PrepRecoveryOp.java | 7 +
.../org/apache/solr/cloud/HttpPartitionTest.java | 2 +
.../handler/admin/ShowFileRequestHandlerTest.java | 11 ++
.../response/TestPrometheusResponseWriter.java | 2 +
.../src/test/org/apache/solr/search/TestSolrJ.java | 188 ---------------------
.../org/apache/solr/util/TestCborDataFormat.java | 6 +-
.../solr/opentelemetry/TestDistributedTracing.java | 36 ++--
.../HttpJettySolrClientCompatibilityTest.java | 10 +-
.../apache/solr/client/solrj/util/ClientUtils.java | 3 +-
.../solr/cloud/AbstractFullDistribZkTestBase.java | 30 ++--
.../org/apache/solr/embedded/JettySolrRunner.java | 8 +-
.../src/java/org/apache/solr/util/SocketProxy.java | 28 ++-
12 files changed, 105 insertions(+), 226 deletions(-)
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/PrepRecoveryOp.java
b/solr/core/src/java/org/apache/solr/handler/admin/PrepRecoveryOp.java
index 9f8bc79413e..8fdbd5aa72f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/PrepRecoveryOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/PrepRecoveryOp.java
@@ -37,6 +37,13 @@ import org.apache.solr.util.TestInjection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ * Long-polling request sent by a recovering replica to the shard leader. The
leader holds the
+ * request open, waiting until it observes the replica reach the expected
state (typically
+ * RECOVERING) in ZooKeeper before responding. This synchronization ensures
the leader is ready to
+ * accept updates from the replica before recovery proceeds. The wait is
bounded by {@code
+ * leaderConflictResolveWait} (default 180s).
+ */
class PrepRecoveryOp implements CoreAdminHandler.CoreAdminOp {
private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
diff --git a/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
b/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
index 3f1e3c19c1b..c799ab5981e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
@@ -83,6 +83,8 @@ public class HttpPartitionTest extends
AbstractFullDistribZkTestBase {
System.setProperty("solr.httpclient.retries", "0");
System.setProperty("solr.retries.on.forward", "0");
System.setProperty("solr.retries.to.followers", "0");
+ // Keep short so stale PrepRecoveryOp handlers time out within the Jetty
graceful stop window
+ System.setProperty("leaderConflictResolveWait", "10000");
}
public HttpPartitionTest() {
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
index 69f3108ba8a..31b0f00b79c 100644
---
a/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
+++
b/solr/core/src/test/org/apache/solr/handler/admin/ShowFileRequestHandlerTest.java
@@ -142,6 +142,7 @@ public class ShowFileRequestHandlerTest extends
SolrTestCaseJ4 {
createShowFileRequest(params("file", "managed-schema", "contentType",
"not/known"));
request.setResponseParser(new InputStreamResponseParser("xml"));
NamedList<Object> response = client.request(request);
+ closeResponseStream(response);
assertEquals(response.get("responseStatus"), 404);
}
@@ -152,6 +153,7 @@ public class ShowFileRequestHandlerTest extends
SolrTestCaseJ4 {
params("file", "/etc/passwd", "contentType", "text/plain;
charset=utf-8"));
request.setResponseParser(new InputStreamResponseParser("xml"));
NamedList<Object> response = client.request(request);
+ closeResponseStream(response);
assertEquals(response.get("responseStatus"), 404);
}
@@ -162,6 +164,7 @@ public class ShowFileRequestHandlerTest extends
SolrTestCaseJ4 {
params("file", "../../solr.xml", "contentType", "application/xml;
charset=utf-8"));
request.setResponseParser(new InputStreamResponseParser("xml"));
NamedList<Object> response = client.request(request);
+ closeResponseStream(response);
assertEquals(response.get("responseStatus"), 400);
}
@@ -176,6 +179,7 @@ public class ShowFileRequestHandlerTest extends
SolrTestCaseJ4 {
"text/plain; charset=utf-8"));
request.setResponseParser(new InputStreamResponseParser("xml"));
NamedList<Object> response = client.request(request);
+ closeResponseStream(response);
assertEquals(response.get("responseStatus"), 400);
}
@@ -202,4 +206,11 @@ public class ShowFileRequestHandlerTest extends
SolrTestCaseJ4 {
// Non-known content types are rejected with 400 error
expectThrows(SolrException.class, () ->
ShowFileRequestHandler.getSafeContentType("foo/bar"));
}
+
+ private void closeResponseStream(NamedList<Object> response) throws
IOException {
+ InputStream stream = (InputStream) response.get("stream");
+ if (stream != null) {
+ stream.close();
+ }
+ }
}
diff --git
a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
index f229b01ac67..610b7267cbe 100644
---
a/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
+++
b/solr/core/src/test/org/apache/solr/response/TestPrometheusResponseWriter.java
@@ -193,6 +193,8 @@ public class TestPrometheusResponseWriter extends
SolrTestCaseJ4 {
try (SolrClient adminClient =
getHttpSolrClient(solrTestRule.getBaseUrl())) {
NamedList<Object> res = adminClient.request(req);
+ InputStream stream = (InputStream) res.get("stream");
+ if (stream != null) stream.close();
// Unknown wt parameter should return a 400 error
assertEquals(400, res.get("responseStatus"));
}
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrJ.java
b/solr/core/src/test/org/apache/solr/search/TestSolrJ.java
deleted file mode 100644
index 4d3c4a3c9d7..00000000000
--- a/solr/core/src/test/org/apache/solr/search/TestSolrJ.java
+++ /dev/null
@@ -1,188 +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.search;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Random;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.ConcurrentUpdateSolrClient;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.util.RTimer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class TestSolrJ extends SolrTestCaseJ4 {
- private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- public void testSolrJ() {
- // docs, producers, connections, sleep_time
- // main(new String[] {"1000000","4", "1", "0"});
-
- // doCommitPerf();
- }
-
- public static SolrClient client;
- public static String idField = "id";
- public static Exception ex;
-
- public static void main(String[] args) throws Exception {
- // String addr = "http://odin.local:80/solr";
- // String addr = "http://odin.local:8983/solr";
- String addr = "http://127.0.0.1:8983/solr";
-
- int i = 0;
- final int nDocs = Integer.parseInt(args[i++]);
- final int nProducers = Integer.parseInt(args[i++]);
- final int nConnections = Integer.parseInt(args[i++]);
- final int maxSleep = Integer.parseInt(args[i++]);
-
- ConcurrentUpdateSolrClient concurrentClient = null;
-
- // server = concurrentClient = new ConcurrentUpdateSolrServer(addr,32,8);
- client =
- concurrentClient =
- new ConcurrentUpdateSolrClient.Builder(addr)
- .withQueueSize(64)
- .withThreadCount(nConnections)
- .build();
-
- client.deleteByQuery("*:*");
- client.commit();
-
- final RTimer timer = new RTimer();
-
- final int docsPerThread = nDocs / nProducers;
-
- Thread[] threads = new Thread[nProducers];
-
- for (int threadNum = 0; threadNum < nProducers; threadNum++) {
- final int base = threadNum * docsPerThread;
-
- threads[threadNum] =
- new Thread("add-thread" + i) {
- @Override
- public void run() {
- try {
- indexDocs(base, docsPerThread, maxSleep);
- } catch (Exception e) {
- log.error("###############################CAUGHT EXCEPTION",
e);
- ex = e;
- }
- }
- };
- threads[threadNum].start();
- }
-
- // optional: wait for commit?
-
- for (int threadNum = 0; threadNum < nProducers; threadNum++) {
- threads[threadNum].join();
- }
-
- if (concurrentClient != null) {
- concurrentClient.blockUntilFinished();
- }
-
- double elapsed = timer.getTime();
- System.out.println(
- "time=" + elapsed + " throughput=" + (nDocs * 1000 / elapsed) + "
Exception=" + ex);
-
- // should server threads be marked as daemon?
- // need a server.close()!!!
- }
-
- @SuppressWarnings({"unchecked"})
- public static SolrInputDocument getDocument(int docnum) {
- SolrInputDocument doc = new SolrInputDocument();
- doc.setField(idField, docnum);
- doc.setField("cat", Integer.toString(docnum & 0x0f));
- doc.setField("name", "my name is " + Integer.toString(docnum & 0xff));
- doc.setField("foo_t", "now is the time for all good men to come to the aid
of their country");
- doc.setField("foo_i", Integer.toString(docnum & 0x0f));
- doc.setField("foo_s", Integer.toString(docnum & 0xff));
- doc.setField("foo_b", Boolean.toString((docnum & 0x01) == 1));
- doc.setField("parent_s", Integer.toString(docnum - 1));
- doc.setField("price", Integer.toString(docnum >> 4));
-
- int golden = (int) 2654435761L;
- int h = docnum * golden;
- int n = (h & 0xff) + 1;
- @SuppressWarnings({"rawtypes"})
- List lst = new ArrayList(n);
- for (int i = 0; i < n; i++) {
- h = (h + i) * golden;
- lst.add(h & 0xfff);
- }
-
- doc.setField("num_is", lst);
- return doc;
- }
-
- public static void indexDocs(int base, int count, int maxSleep)
- throws IOException, SolrServerException {
- Random r = new Random(base);
-
- for (int i = base; i < count + base; i++) {
- if ((i & 0xfffff) == 0) {
- System.out.print("\n% " + new Date() + "\t" + i + "\t");
- System.out.flush();
- }
-
- if ((i & 0xffff) == 0) {
- System.out.print(".");
- System.out.flush();
- }
-
- SolrInputDocument doc = getDocument(i);
- client.add(doc);
-
- if (maxSleep > 0) {
- int sleep = r.nextInt(maxSleep);
- try {
- Thread.sleep(sleep);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.error("interrupted", e);
- throw new RuntimeException(e);
- }
- }
- }
- }
-
- public void doCommitPerf() throws Exception {
-
- try (SolrClient client = getHttpSolrClient("http://127.0.0.1:8983/solr")) {
-
- final RTimer timer = new RTimer();
-
- for (int i = 0; i < 10000; i++) {
- SolrInputDocument doc = new SolrInputDocument();
- doc.addField("id", Integer.toString(i % 13));
- client.add(doc);
- client.commit(true, true, true);
- }
-
- System.out.println("TIME: " + timer.getTime());
- }
- }
-}
diff --git a/solr/core/src/test/org/apache/solr/util/TestCborDataFormat.java
b/solr/core/src/test/org/apache/solr/util/TestCborDataFormat.java
index 056e9536529..c98ef9f01eb 100644
--- a/solr/core/src/test/org/apache/solr/util/TestCborDataFormat.java
+++ b/solr/core/src/test/org/apache/solr/util/TestCborDataFormat.java
@@ -135,8 +135,10 @@ public class TestCborDataFormat extends SolrCloudTestCase {
request.setResponseParser(new InputStreamResponseParser(wt));
}
result = client.request(request, testCollection);
- InputStream inputStream = (InputStream) result.get("stream");
- byte[] b = inputStream.readAllBytes();
+ byte[] b;
+ try (InputStream inputStream = (InputStream) result.get("stream")) {
+ b = inputStream.readAllBytes();
+ }
System.out.println(wt + "_time : " + timer.getTime());
System.out.println(wt + "_size : " + b.length);
return b;
diff --git
a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
index ad5b474b771..5433c11fd1d 100644
---
a/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
+++
b/solr/modules/opentelemetry/src/test/org/apache/solr/opentelemetry/TestDistributedTracing.java
@@ -28,6 +28,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
@@ -41,6 +42,7 @@ import org.apache.solr.client.solrj.response.V2Response;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.RetryUtil;
import org.apache.solr.util.stats.MetricUtils;
import org.apache.solr.util.tracing.TraceUtils;
import org.junit.AfterClass;
@@ -92,7 +94,7 @@ public class TestDistributedTracing extends SolrCloudTestCase
{
// Indexing
cloudClient.add(COLLECTION, sdoc("id", "1"));
- var finishedSpans = getAndClearSpans();
+ var finishedSpans = getAndClearSpans(1);
finishedSpans.removeIf(
span ->
span.getAttributes().get(TraceUtils.TAG_HTTP_URL) == null
@@ -111,7 +113,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
// Searching
cloudClient.query(COLLECTION, new SolrQuery("*:*"));
- finishedSpans = getAndClearSpans();
+ finishedSpans = getAndClearSpans(1);
finishedSpans.removeIf(
span ->
span.getAttributes().get(TraceUtils.TAG_HTTP_URL) == null
@@ -138,11 +140,11 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
request.setResponseParser(new
InputStreamResponseParser(MetricUtils.PROMETHEUS_METRICS_WT));
NamedList<Object> rsp = cloudClient.request(request);
((InputStream) rsp.get("stream")).close();
- var finishedSpans = getAndClearSpans();
+ var finishedSpans = getAndClearSpans(1);
assertEquals("get:/admin/metrics", finishedSpans.get(0).getName());
CollectionAdminRequest.listCollections(cloudClient);
- finishedSpans = getAndClearSpans();
+ finishedSpans = getAndClearSpans(1);
assertEquals("list:/admin/collections", finishedSpans.get(0).getName());
}
@@ -155,7 +157,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
.withPayload("{}")
.build()
.process(cloudClient);
- var finishedSpans = getAndClearSpans();
+ var finishedSpans = getAndClearSpans(1);
assertEquals("post:/collections/{collection}/reload",
finishedSpans.get(0).getName());
assertCollectionName(finishedSpans.get(0), COLLECTION);
@@ -165,7 +167,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
.withParams(params("commit", "true"))
.build()
.process(cloudClient);
- finishedSpans = getAndClearSpans();
+ finishedSpans = getAndClearSpans(1);
assertEquals("post:/c/{collection}/update/json",
finishedSpans.get(0).getName());
assertCollectionName(finishedSpans.get(0), COLLECTION);
@@ -175,7 +177,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
.withParams(params("q", "id:9"))
.build()
.process(cloudClient);
- finishedSpans = getAndClearSpans();
+ finishedSpans = getAndClearSpans(1);
assertEquals("get:/c/{collection}/select", finishedSpans.get(0).getName());
assertCollectionName(finishedSpans.get(0), COLLECTION);
assertEquals(1, ((SolrDocumentList)
v2Response.getResponse().get("response")).getNumFound());
@@ -191,7 +193,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
CollectionAdminRequest.ColStatus a1 =
CollectionAdminRequest.collectionStatus(COLLECTION);
CollectionAdminResponse r1 = a1.process(cluster.getSolrClient());
assertEquals(0, r1.getStatus());
- var finishedSpans = getAndClearSpans();
+ var finishedSpans = getAndClearSpans(1);
var parentTraceId = getRootTraceId(finishedSpans);
for (var span : finishedSpans) {
if (isRootSpan(span)) {
@@ -239,7 +241,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
// db.instance=testInternalCollectionApiCommands_shard2_replica_n4
// db.instance=testInternalCollectionApiCommands_shard1_replica_n2
- var finishedSpans = getAndClearSpans();
+ var finishedSpans = getAndClearSpans(1);
var s0 = finishedSpans.remove(0);
assertCollectionName(s0, collection);
assertEquals("create:/admin/collections", s0.getName());
@@ -280,7 +282,7 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
// db.instance=testInternalCollectionApiCommands_shard2_replica_n4
// db.instance=testInternalCollectionApiCommands_shard1_replica_n6
- var finishedSpans = getAndClearSpans();
+ var finishedSpans = getAndClearSpans(1);
var s0 = finishedSpans.remove(0);
assertCollectionName(s0, collection);
assertEquals("delete:/admin/collections", s0.getName());
@@ -327,7 +329,21 @@ public class TestDistributedTracing extends
SolrCloudTestCase {
}
static List<SpanData> getAndClearSpans() {
+ return getAndClearSpans(0);
+ }
+
+ static List<SpanData> getAndClearSpans(int minExpected) {
InMemorySpanExporter exporter =
CustomTestOtelTracerConfigurator.getInMemorySpanExporter();
+ try {
+ RetryUtil.retryUntil(
+ "Timed out waiting for " + minExpected + " span(s)",
+ 250,
+ 20,
+ TimeUnit.MILLISECONDS,
+ () -> exporter.getFinishedSpanItems().size() >= minExpected);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
List<SpanData> result = new ArrayList<>(exporter.getFinishedSpanItems());
Collections.reverse(result); // nicer to see spans chronologically
exporter.reset();
diff --git
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
index 82b3fd8e83a..5badd40211a 100644
---
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
+++
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
@@ -34,13 +34,13 @@ import org.apache.solr.util.SolrJettyTestRule;
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
-import org.junit.ClassRule;
+import org.junit.Rule;
@LogLevel("org.eclipse.jetty.client=DEBUG;org.eclipse.jetty.util=DEBUG")
@SolrTestCaseJ4.SuppressSSL
public class HttpJettySolrClientCompatibilityTest extends SolrTestCaseJ4 {
- @ClassRule public static SolrJettyTestRule solrTestRule = new
SolrJettyTestRule();
+ @Rule public SolrJettyTestRule solrTestRule = new SolrJettyTestRule();
public void testSystemPropertyFlag() {
System.setProperty("solr.http1", "true");
@@ -74,8 +74,6 @@ public class HttpJettySolrClientCompatibilityTest extends
SolrTestCaseJ4 {
client.query(new SolrQuery("*:*"), SolrRequest.METHOD.GET);
} catch (RemoteSolrException ignored) {
}
- } finally {
- solrTestRule.reset();
}
}
@@ -100,8 +98,6 @@ public class HttpJettySolrClientCompatibilityTest extends
SolrTestCaseJ4 {
client.query(new SolrQuery("*:*"), SolrRequest.METHOD.GET);
} catch (RemoteSolrException ignored) {
}
- } finally {
- solrTestRule.reset();
}
}
@@ -133,8 +129,6 @@ public class HttpJettySolrClientCompatibilityTest extends
SolrTestCaseJ4 {
} catch (SolrServerException e) {
// expected
}
- } finally {
- solrTestRule.reset();
}
}
}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
index 60005d50e5a..bf06c491d03 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
@@ -28,6 +28,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
@@ -69,7 +70,7 @@ public class ClientUtils {
SolrRequest<?> solrRequest, String serverRootUrl, String collection)
throws MalformedURLException {
- String basePath = serverRootUrl;
+ String basePath = Objects.requireNonNull(serverRootUrl);
if (solrRequest.getApiVersion() == SolrRequest.ApiVersion.V2) {
basePath = addNormalV2ApiRoot(basePath);
}
diff --git
a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
index d4b81d99ea6..e2062527680 100644
---
a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
+++
b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
@@ -90,6 +90,7 @@ import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.RetryUtil;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.TimeSource;
@@ -378,12 +379,17 @@ public abstract class AbstractFullDistribZkTestBase
extends BaseDistributedSearc
controlJettyDir, useJettyDataDir ? getDataDir(testDir +
"/control/data") : null);
controlJetty.start();
try (CloudSolrClient client = createCloudClient("control_collection")) {
- assertEquals(
- 0,
- CollectionAdminRequest.createCollection("control_collection",
"conf1", 1, 1)
- .setCreateNodeSet(controlJetty.getNodeName())
- .process(client)
- .getStatus());
+ RetryUtil.retryOnException(
+ SolrException.class,
+ 30000,
+ 1000,
+ () ->
+ assertEquals(
+ 0,
+
CollectionAdminRequest.createCollection("control_collection", "conf1", 1, 1)
+ .setCreateNodeSet(controlJetty.getNodeName())
+ .process(client)
+ .getStatus()));
waitForActiveReplicaCount(client, "control_collection", 1);
}
@@ -2261,12 +2267,14 @@ public abstract class AbstractFullDistribZkTestBase
extends BaseDistributedSearc
customThreadPool.execute(() -> IOUtils.closeQuietly(cloudClient));
- ExecutorUtil.shutdownAndAwaitTermination(customThreadPool);
-
- coreClients.clear();
- solrClientByCollection.clear();
+ try {
+ ExecutorUtil.shutdownAndAwaitTermination(customThreadPool);
+ } finally {
+ coreClients.clear();
+ solrClientByCollection.clear();
- super.destroyServers();
+ super.destroyServers();
+ }
}
@Override
diff --git
a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
index f355b057aa4..10906c15e66 100644
--- a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
+++ b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
@@ -579,6 +579,10 @@ public class JettySolrRunner implements SolrBackend {
IOUtils.closeQuietly(jettySolrClient);
jettySolrClient = null;
+ if (enableProxy) {
+ proxy.close();
+ }
+
QueuedThreadPool qtp = (QueuedThreadPool) server.getThreadPool();
ReservedThreadExecutor rte = qtp.getBean(ReservedThreadExecutor.class);
@@ -620,10 +624,6 @@ public class JettySolrRunner implements SolrBackend {
} while (!server.isStopped());
} finally {
- if (enableProxy) {
- proxy.close();
- }
-
if (prevContext != null) {
MDC.setContextMap(prevContext);
} else {
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SocketProxy.java
b/solr/test-framework/src/java/org/apache/solr/util/SocketProxy.java
index 2e0c6ea388b..2ee70a4f9ad 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SocketProxy.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SocketProxy.java
@@ -37,8 +37,30 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Kindly borrowed the idea and base implementation from the ActiveMQ project;
useful for blocking
- * traffic on a specified port.
+ * A TCP-level proxy used in tests to simulate network partitions. It listens
on its own port and
+ * forwards all traffic to the target Jetty server. Key operations:
+ *
+ * <ul>
+ * <li>{@link #close()} — drops all connections and stops accepting new ones
(simulates connection
+ * refused)
+ * <li>{@link #pause()} / {@link #goOn()} — freezes all data flow while
keeping sockets open
+ * (simulates a hung network)
+ * <li>{@link #reopen()} — restores connectivity on the same port after a
{@link #close()}
+ * </ul>
+ *
+ * <p>The proxy sits <em>in front of</em> Jetty by design: the Jetty server
and ZooKeeper heartbeats
+ * continue running normally while the proxy makes the node appear unreachable
to clients. A Jetty
+ * Handler cannot achieve this because it requires a TCP connection to already
exist.
+ *
+ * <p>Implementation note: each accepted connection creates a {@link Bridge}
backed by two {@link
+ * Bridge.Pump} threads (one per direction), using blocking I/O borrowed from
ActiveMQ.
+ *
+ * <p>TODO: Replace the blocking-I/O {@link Bridge.Pump} threads with a NIO
{@code
+ * java.nio.channels.Selector}-based implementation. The current model spawns
2 OS threads per
+ * connection; under test load with many concurrent connections this is
wasteful, and the {@link
+ * #PUMP_SOCKET_TIMEOUT_MS} timeout means threads linger for up to 100 s after
connections are
+ * dropped. A single-selector loop would handle all connections with O(1)
threads, as Jetty itself
+ * does internally.
*/
public class SocketProxy {
@@ -81,6 +103,7 @@ public class SocketProxy {
this.usesSSL = useSSL;
serverSocket = createServerSocket(useSSL);
serverSocket.setReuseAddress(true);
+ serverSocket.setSoTimeout(1000);
if (receiveBufferSize > 0) {
serverSocket.setReceiveBufferSize(receiveBufferSize);
}
@@ -188,6 +211,7 @@ public class SocketProxy {
}
serverSocket = createServerSocket(usesSSL);
serverSocket.setReuseAddress(true);
+ serverSocket.setSoTimeout(1000);
if (receiveBufferSize > 0) {
serverSocket.setReceiveBufferSize(receiveBufferSize);
}