Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
*
* <p>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.
*
* <ul>
* <li>solr.solrclientcache.request.timeout.ms - total time allowed for a request/response
* (including streaming the entire result set).
* <li>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).
* </ul>
*/
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

Expand Down Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}