diff --git a/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java b/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
index 3503bdb90793..4255be665e67 100644
--- a/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
+++ b/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
@@ -29,20 +29,47 @@
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.SolrHttpConstants;
import org.apache.solr.common.AlreadyClosedException;
+import org.apache.solr.common.util.EnvUtils;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.URLUtil;
/** The SolrClientCache caches SolrClients, so they can be reused by different TupleStreams. */
public class SolrClientCache implements Closeable {
- // Set the floor for timeouts to 60 seconds.
- // Timeouts can be increased by setting the system properties defined below.
+ /**
+ * Sentinel value meaning "no practical timeout" for streaming clients.
+ *
+ *
Tuple streams (export, analytics, etc.) can run for a very long time. We default both the
+ * total request timeout and idle timeout to this value so that long-lived result streams are not
+ * artificially killed. Lower-level TCP/OS timeouts still apply.
+ */
+ static final long NO_TIMEOUT = Long.MAX_VALUE;
+
+ // Connection timeout floor (only used when we create the underlying transport ourselves).
private static final int MIN_TIMEOUT = 60000;
private static final int minConnTimeout =
Math.max(
Integer.getInteger(SolrHttpConstants.PROP_CONNECTION_TIMEOUT, MIN_TIMEOUT), MIN_TIMEOUT);
- private static final int minSocketTimeout =
- Math.max(Integer.getInteger(SolrHttpConstants.PROP_SO_TIMEOUT, MIN_TIMEOUT), MIN_TIMEOUT);
+
+ /**
+ * Configurable timeouts for clients created by this cache.
+ *
+ *
These default to "no timeout" because this cache is used almost exclusively by streaming
+ * expressions that legitimately produce long-running responses. Use the system properties (or Env
+ * var equivalents via EnvUtils) to tighten them if desired for your environment.
+ *
+ *
+ * - solr.solrclientcache.request.timeout.ms - total time allowed for a request/response
+ * (including streaming the entire result set).
+ *
- solr.solrclientcache.idle.timeout.ms - max idle time between data on the connection
+ * (affects how long we wait for the first response bytes and gaps during streaming).
+ *
+ */
+ private static final long streamingRequestTimeoutMs =
+ EnvUtils.getPropertyAsLong("solr.solrclientcache.request.timeout.ms", NO_TIMEOUT);
+
+ private static final long streamingIdleTimeoutMs =
+ EnvUtils.getPropertyAsLong("solr.solrclientcache.idle.timeout.ms", NO_TIMEOUT);
protected String basicAuthCredentials = null; // Only support with the httpJettySolrClient
@@ -155,13 +182,26 @@ public synchronized SolrClient getHttpSolrClient(String baseUrl) {
} else {
builder.withConnectionTimeout(minConnTimeout, TimeUnit.MILLISECONDS);
}
- builder.withIdleTimeout(
- Math.max(minSocketTimeout, builder.getIdleTimeoutMillis()), TimeUnit.MILLISECONDS);
+
+ // Streaming / TupleStream use cases need very long (or unlimited) timeouts for both total
+ // request duration and idle time between chunks. The header-wait path in Http*SolrClient also
+ // uses the client's idle timeout, so a high value here enables slow-starting long queries.
+ builder.withIdleTimeout(streamingIdleTimeoutMs, TimeUnit.MILLISECONDS);
+ builder.withRequestTimeout(streamingRequestTimeoutMs, TimeUnit.MILLISECONDS);
+
builder.withOptionalBasicAuthCredentials(basicAuthCredentials);
return builder;
}
+ /**
+ * Visible for testing. Returns the builder that would be used so tests can inspect the configured
+ * timeouts without using reflection or forbidden APIs.
+ */
+ HttpSolrClient.BuilderBase, ?> newHttpSolrClientBuilderForTest(String url) {
+ return newHttpSolrClientBuilder(url, null);
+ }
+
@Override
public synchronized void close() {
if (isClosed.compareAndSet(false, true)) {
diff --git a/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/SolrClientCacheTest.java b/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/SolrClientCacheTest.java
index 809a6abe3635..186fa4c3665e 100644
--- a/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/SolrClientCacheTest.java
+++ b/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/SolrClientCacheTest.java
@@ -110,4 +110,20 @@ public void testZkACLsUsedWithDifferentChroot() {
CloudSolrClient.CloudSolrClientConnection.parse(zkClient().getZkServerAddress()));
}
}
+
+ @Test
+ public void testStreamingClientsDefaultToNoPracticalTimeout() {
+ try (SolrClientCache cache = new SolrClientCache()) {
+ // Use the test helper so we can inspect configured values without reflection.
+ var builder = cache.newHttpSolrClientBuilderForTest("http://localhost:8983/solr");
+ Assert.assertEquals(
+ "request timeout for streaming cache clients should default to no practical limit",
+ SolrClientCache.NO_TIMEOUT,
+ builder.getRequestTimeoutMillis());
+ Assert.assertEquals(
+ "idle timeout for streaming cache clients should default to no practical limit",
+ SolrClientCache.NO_TIMEOUT,
+ builder.getIdleTimeoutMillis());
+ }
+ }
}