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. + * + *

+ */ + 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()); + } + } }