diff --git a/changelog/unreleased/cli-configurable-timeouts.yml b/changelog/unreleased/cli-configurable-timeouts.yml new file mode 100644 index 000000000000..46b6b4353a6d --- /dev/null +++ b/changelog/unreleased/cli-configurable-timeouts.yml @@ -0,0 +1,9 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: bin/solr CLI commands now fail fast (3s connect / 30s idle) against an unresponsive + node instead of inheriting SolrJ's 60s/600s bulk-indexing defaults. +type: changed +authors: + - name: Eric Pugh +links: +- name: PR#4955 + url: https://github.com/apache/solr/pull/4955 diff --git a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java index 3c5d2a98ad6f..ef53dabb8d14 100644 --- a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java +++ b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java @@ -63,6 +63,16 @@ private CLIUtils() {} private static final long MAX_WAIT_FOR_CORE_LOAD_NANOS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MINUTES); + /** + * Connect and idle timeouts used for CLI-initiated Solr connections. These are much shorter than + * {@link org.apache.solr.client.solrj.impl.SolrHttpConstants}' defaults, which are sized for + * long-running requests like bulk indexing or replica recovery; a CLI command run by a human at a + * terminal should fail fast instead of hanging for minutes against an unresponsive node. + */ + public static final int CLI_CONNECTION_TIMEOUT_SECONDS = 3; + + public static final int CLI_IDLE_TIMEOUT_SECONDS = 30; + private static CloudSolrClient.CloudSolrClientConnection resolveSolrConnectionFromCli( CommandLine cli) throws IOException { String solrConnection = @@ -146,6 +156,8 @@ public static SolrClient getSolrClient(String solrUrl, String credentials, boole var builder = new HttpJettySolrClient.Builder(solrUrl) .withMaxConnectionsPerHost(32) + .withIdleTimeout(CLI_IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withConnectionTimeout(CLI_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) .withKeyStoreReloadInterval(-1, TimeUnit.SECONDS) .withOptionalBasicAuthCredentials(credentials); @@ -300,7 +312,11 @@ public static String solrUrlFromConnection( if (!solrConnection.isZookeeper()) { return normalizeSolrUrl(solrConnection.quorumItems().get(0), false); } - var builder = new HttpJettySolrClient.Builder().withOptionalBasicAuthCredentials(credentials); + var builder = + new HttpJettySolrClient.Builder() + .withIdleTimeout(CLI_IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withConnectionTimeout(CLI_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withOptionalBasicAuthCredentials(credentials); try (CloudSolrClient cloudSolrClient = getCloudSolrClient(solrConnection, builder)) { Set liveNodes = cloudSolrClient.getClusterState().getLiveNodes(); if (liveNodes.isEmpty()) diff --git a/solr/core/src/java/org/apache/solr/cli/ExportTool.java b/solr/core/src/java/org/apache/solr/cli/ExportTool.java index 03dacb31f48f..c83cccb04fea 100644 --- a/solr/core/src/java/org/apache/solr/cli/ExportTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ExportTool.java @@ -366,7 +366,11 @@ DocsSink getSink() { abstract void exportDocs() throws Exception; void fetchUniqueKey() throws SolrServerException, IOException { - var builder = new HttpJettySolrClient.Builder().withOptionalBasicAuthCredentials(credentials); + var builder = + new HttpJettySolrClient.Builder() + .withIdleTimeout(CLIUtils.CLI_IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withConnectionTimeout(CLIUtils.CLI_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withOptionalBasicAuthCredentials(credentials); solrClient = new CloudSolrClient.Builder(List.of(baseurl)).withHttpClientBuilder(builder).build(); diff --git a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java index 005be05cec77..89cfe21619fd 100644 --- a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java +++ b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java @@ -29,6 +29,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; @@ -129,7 +130,10 @@ public void runImpl(CommandLine cli) throws Exception { cli.getOptionValue(COLLECTION_NAME_OPTION), cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION)); var builder = - new HttpJettySolrClient.Builder().withOptionalBasicAuthCredentials(params.credentials()); + new HttpJettySolrClient.Builder() + .withIdleTimeout(CLIUtils.CLI_IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withConnectionTimeout(CLIUtils.CLI_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withOptionalBasicAuthCredentials(params.credentials()); try (var cloudSolrClient = CLIUtils.getCloudSolrClient(solrConnection, builder)) { echoIfVerbose("Connecting to Solr at " + solrConnection.toString()); runCloudTool(cloudSolrClient, params); diff --git a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java index 5f02b57ece20..e71acfd7cbc1 100644 --- a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java +++ b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java @@ -167,6 +167,8 @@ public void runCommand(String baseUrl, String root, String credentials) throws I var builder = new HttpJettySolrClient.Builder(URLUtil.extractBaseUrl(baseUrl)) .withDefaultCollection(URLUtil.extractCoreFromCoreUrl(baseUrl)) + .withIdleTimeout(CLIUtils.CLI_IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withConnectionTimeout(CLIUtils.CLI_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) .withKeyStoreReloadInterval(-1, TimeUnit.SECONDS) .withOptionalBasicAuthCredentials(credentials); try (SolrClient client = builder.build()) { diff --git a/solr/core/src/java/org/apache/solr/cli/StreamTool.java b/solr/core/src/java/org/apache/solr/cli/StreamTool.java index 798707425780..41cc28fb8661 100644 --- a/solr/core/src/java/org/apache/solr/cli/StreamTool.java +++ b/solr/core/src/java/org/apache/solr/cli/StreamTool.java @@ -34,6 +34,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; @@ -426,7 +427,10 @@ void runStream( private StreamContext createStreamContext( CloudSolrClient.CloudSolrClientConnection solrConnection, String credentials) { var jettyClientBuilder = new HttpJettySolrClient.Builder(); - jettyClientBuilder.withOptionalBasicAuthCredentials(credentials); + jettyClientBuilder + .withIdleTimeout(CLIUtils.CLI_IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withConnectionTimeout(CLIUtils.CLI_CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .withOptionalBasicAuthCredentials(credentials); HttpJettySolrClient client = jettyClientBuilder.build(); // subclass so we can ensure our client is closed when the cache is closed