diff --git a/changelog/unreleased/cli-configurable-timeouts.yml b/changelog/unreleased/cli-configurable-timeouts.yml new file mode 100644 index 00000000000..46b6b4353a6 --- /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 f60d03c3574..4be1dc9f4b4 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); @@ -258,7 +270,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 b63c2ba002d..38b5753d410 100644 --- a/solr/core/src/java/org/apache/solr/cli/ExportTool.java +++ b/solr/core/src/java/org/apache/solr/cli/ExportTool.java @@ -254,7 +254,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 7311877cc78..8c5b8823ef5 100644 --- a/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java +++ b/solr/core/src/java/org/apache/solr/cli/HealthcheckTool.java @@ -27,6 +27,7 @@ import java.util.List; 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; @@ -93,7 +94,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 2764ba4b9fb..9af4082fdc0 100644 --- a/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java +++ b/solr/core/src/java/org/apache/solr/cli/PostLogsTool.java @@ -119,6 +119,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 da8beec6657..67ae4825a84 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; @@ -303,7 +304,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