Skip to content
Merged
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
45 changes: 45 additions & 0 deletions client-v2/src/main/java/com/clickhouse/client/api/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,51 @@ public Map<String, String> getConfiguration() {
return readOnlyConfig;
}

/**
* Sets the network timeout for subsequent requests.
* <p>
* The timeout is measured in milliseconds. A value of {@code 0} disables the timeout.
* Requests that have already been created, including their retries, keep the timeout they
* started with. An operation-level timeout in {@link QuerySettings} or {@link InsertSettings}
* overrides this client-level value.
*
* @param timeout network timeout in milliseconds
*/
public void setNetworkTimeout(int timeout) {
ValidationUtils.checkRange(timeout, 0, Integer.MAX_VALUE,
ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey());
configuration.put(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), timeout);
}

/**
* Sets the network timeout for subsequent requests.
*
* @param timeout network timeout value
* @param unit timeout unit
* @see #setNetworkTimeout(int)
*/
public void setNetworkTimeout(long timeout, ChronoUnit unit) {
ValidationUtils.checkNotNull(unit, "unit");
long timeoutMillis;
try {
timeoutMillis = Duration.of(timeout, unit).toMillis();
} catch (ArithmeticException e) {
throw new IllegalArgumentException("Network timeout is outside the supported millisecond range", e);
}
ValidationUtils.checkRange(timeoutMillis, 0, Integer.MAX_VALUE,
ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey());
setNetworkTimeout((int) timeoutMillis);
}

/**
* Returns the current client-level network timeout in milliseconds.
*
* @return network timeout in milliseconds; {@code 0} means disabled
*/
public int getNetworkTimeout() {
return ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.<Integer>getOrDefault(configuration);
}

/**
* Returns operation ({@link ClientConfigProperties#MAX_EXECUTION_TIME}) timeout value in milliseconds.
* @return timeout value in milliseconds.
Expand Down
81 changes: 81 additions & 0 deletions client-v2/src/test/java/com/clickhouse/client/ClientTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.clickhouse.client.api.ConnectionInitiationException;
import com.clickhouse.client.api.ConnectionReuseStrategy;
import com.clickhouse.client.api.ServerException;
import com.clickhouse.client.api.command.CommandResponse;
import com.clickhouse.client.api.command.CommandSettings;
import com.clickhouse.client.api.enums.Protocol;
import com.clickhouse.client.api.insert.InsertSettings;
Expand All @@ -24,6 +25,9 @@
import com.clickhouse.client.config.ClickHouseClientOption;
import com.clickhouse.data.ClickHouseFormat;
import com.clickhouse.data.ClickHouseVersion;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -35,6 +39,7 @@

import java.io.ByteArrayInputStream;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -833,6 +838,71 @@ public void testQueryFormatBuilder() {
}
}

@Test(timeOut = 15000)
public void testRuntimeNetworkTimeoutAppliesToSubsequentRequests() throws Exception {
WireMockServer server = new WireMockServer(WireMockConfiguration.options()
.bindAddress("127.0.0.1").dynamicPort());
server.start();
try {
server.stubFor(WireMock.post(WireMock.anyUrl())
.willReturn(WireMock.aResponse().withFixedDelay(500)
.withHeader("X-ClickHouse-Summary", "{\"read_rows\":\"0\",\"read_bytes\":\"0\"}")));

try (Client client = new Client.Builder()
.addEndpoint("http://127.0.0.1:" + server.port())
.setUsername("default")
.setPassword("")
.setDefaultDatabase("default")
.setSocketTimeout(200)
.setMaxRetries(0)
.compressServerResponse(false)
.build()) {
Assert.assertEquals(client.getNetworkTimeout(), 200);

client.setNetworkTimeout(50);
Assert.assertEquals(client.getNetworkTimeout(), 50);
Exception error = Assert.expectThrows(Exception.class,
() -> client.execute("SELECT 1").get(5, TimeUnit.SECONDS));
Assert.assertTrue(hasCause(error, SocketTimeoutException.class),
"Expected a socket timeout, got: " + error);

CommandSettings requestSettings = new CommandSettings();
requestSettings.setNetworkTimeout(0, MILLIS);
try (CommandResponse response = client.execute("SELECT 1", requestSettings)
.get(5, TimeUnit.SECONDS)) {
Assert.assertEquals(response.getReadRows(), 0);
}
Assert.assertEquals(client.getNetworkTimeout(), 50);

client.setNetworkTimeout(0, MILLIS);
Assert.assertEquals(client.getNetworkTimeout(), 0);
try (CommandResponse response = client.execute("SELECT 1").get(5, TimeUnit.SECONDS)) {
Assert.assertEquals(response.getReadRows(), 0);
}
}
} finally {
server.stop();
}
}

@Test
public void testNetworkTimeoutUnitConversionAndValidation() {
try (Client client = new Client.Builder()
.addEndpoint("http://127.0.0.1:1")
.setUsername("default")
.setPassword("")
.build()) {
client.setNetworkTimeout(2, SECONDS);
Assert.assertEquals(client.getNetworkTimeout(), 2000);

Assert.assertThrows(IllegalArgumentException.class, () -> client.setNetworkTimeout(-1));
Assert.assertThrows(IllegalArgumentException.class, () -> client.setNetworkTimeout(-1, MILLIS));
Assert.assertThrows(IllegalArgumentException.class,
() -> client.setNetworkTimeout((long) Integer.MAX_VALUE + 1, MILLIS));
Assert.assertThrows(IllegalArgumentException.class, () -> client.setNetworkTimeout(1, null));
}
}

@Test(groups = {"integration"})
public void testOverrideSettings() throws Exception {
final String clientTimezone = "America/Los_Angeles";
Expand Down Expand Up @@ -866,6 +936,17 @@ private void assertTableNotFoundInSession(Client client, String tableName, Query
}
}

private static boolean hasCause(Throwable error, Class<? extends Throwable> type) {
Throwable current = error;
while (current != null) {
if (type.isInstance(current)) {
return true;
}
current = current.getCause();
}
return false;
}

protected Client.Builder newClient() {
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
boolean isSecure = isCloud();
Expand Down
4 changes: 2 additions & 2 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This document lists stable, user-visible behavior in `client-v2` and `jdbc-v2` t
- No HTTP wire logging: The connections of the client, with and without the connection pool (`connection_pool_enabled`), never write request or response bytes to the Apache HttpClient wire logger (`org.apache.hc.client5.http.wire`), also when that logger or the root logger is set to `DEBUG`. These bytes contain credentials, SQL, and data. No option turns wire logging on. The client does not change the logging configuration of the application, and the `DEBUG` logs of all other client and Apache HttpClient loggers are not changed: the Apache HttpClient header logger (`org.apache.hc.client5.http.headers`) still writes request headers, including `Authorization`, at `DEBUG`. `jdbc-v2` connections use the same transport. This is a rule for all transports of the client, also for transports that are added in the future (TCP, HTTP, or other): a transport must never write raw request or response bytes to a log.
- Runtime credential updates: Existing `Client` instances can update username/password or bearer-token credentials for subsequent requests without rebuilding the client.
- Proxy support: Can send requests through configured HTTP proxies, including proxy credentials.
- Connection and socket tuning: Exposes pool sizing, keep-alive, reuse strategy, connect/request/socket timeouts, and low-level socket options. The socket buffer options `socket_rcvbuf` and `socket_sndbuf` (`Client.Builder#setSocketRcvbuf`/`#setSocketSndbuf`) have no default value: unless the application sets one, the buffers are left to the operating system, which sizes and auto-tunes them. Setting a fixed size disables that auto-tuning and is capped by the operating system limits, so it is not recommended unless a measurement shows a benefit.
- Connection and socket tuning: Exposes pool sizing, keep-alive, reuse strategy, connect/request/socket timeouts, and low-level socket options. `Client#setNetworkTimeout(int)` (or the `long`/`ChronoUnit` overload) changes the socket-operation timeout for subsequent requests at runtime; requests already created keep their captured value, and an operation-level timeout overrides the client value. The socket buffer options `socket_rcvbuf` and `socket_sndbuf` (`Client.Builder#setSocketRcvbuf`/`#setSocketSndbuf`) have no default value: unless the application sets one, the buffers are left to the operating system, which sizes and auto-tunes them. Setting a fixed size disables that auto-tuning and is capped by the operating system limits, so it is not recommended unless a measurement shows a benefit.
- Query execution: Executes SQL asynchronously and returns streaming query responses with response metadata and metrics.
- Query settings: Supports per-query database selection, output format, execution limits, roles, log comments, headers, reusable `Session` objects, session settings, server settings, and network timeout overrides. Settings explicitly set to `null` or empty string will not be sent to the server. The default format (`RowBinaryWithNamesAndTypes`) is configured at the client level (`ClientConfigProperties.INPUT_OUTPUT_FORMAT`), sending the `X-ClickHouse-Format` request header. ClickHouse `26.8+` uses that request header in preference to a `FORMAT` clause in the SQL query. Configuring `format=` (empty) or `null` omits the format request header, allowing explicit SQL `FORMAT` clauses to take effect; however, on a JDBC connection this is an expert setting, as queries without a `FORMAT` clause fall back to server `default_format` (`TabSeparated`), which JDBC rejects.
- Parameterized SQL: Accepts named query parameters and can send them through supported HTTP request encodings.
Expand Down Expand Up @@ -77,7 +77,7 @@ Compatibility-sensitive traits:
- Driver and client properties: Separates JDBC-specific properties from passthrough client options used by the underlying `client-v2` transport. A passthrough client option that has no default value is reported with a `null` default by `Driver#getPropertyInfo` and is not sent to the transport unless the connection sets it — this is the case for the socket buffer properties `socket_rcvbuf` and `socket_sndbuf`, which are left to the operating system.
- Metrics recording: The `jdbc_metrics_recorder` property names the `client-v2` `MetricsRecorder` implementation the connection reports the metrics of its operations to. A JDBC URL carries strings only, so the value is the fully-qualified class name of a recorder with a public no-argument constructor, and each connection creates its own instance and registers it with its own client. A class that is not on the classpath, does not implement `MetricsRecorder`, or cannot be instantiated is rejected when the connection is created. When the property is not set, nothing is recorded.
- DataSource support: Provides a JDBC `DataSource` implementation backed by the same driver configuration model.
- Connection lifecycle: Supports connection close, validity checks, ping-based health checks, and network timeout management.
- Connection lifecycle: Supports connection close, validity checks, ping-based health checks, and network timeout management. `Connection#getNetworkTimeout()` returns the underlying client's current network timeout, initially configured by `socket_timeout`, and `setNetworkTimeout` updates that client-level value so subsequent requests from existing as well as new statements use it. When a connection pool saves the timeout, sets another one, and restores the saved one, the configured `socket_timeout` stays in effect. `setNetworkTimeout(executor, 0)` disables the socket timeout.
- Underlying client access: `ConnectionImpl#getClient()` exposes the connection-owned `client-v2` instance for operations that are not representable through the JDBC API, including direct consumption of raw response streams.
- Schema and database context: Supports database selection through URL, `setSchema`, `USE`, and statement-level settings.
- Non-transactional operation: Exposes ClickHouse-appropriate transaction behavior with auto-commit semantics and unsupported transactional features.
Expand Down
4 changes: 2 additions & 2 deletions docs/integration-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,11 @@ See [SSLExamples](../examples/client-v2/src/main/java/com/clickhouse/examples/cl
Timeouts are critical parameters that directly impact application stability under load and over long distances:

- **Connection timeout** (`.setConnectTimeout()`): The TCP connect timeout. Setting this value too low can cause failures when the application and server are in different geographical regions. Additionally, connection timeouts are closely tied to the connection pool: if the application issues concurrent requests that exceed the available pool size, it may manifest as a connection timeout because no free connections are present.
- **Socket timeout** (`.setSocketTimeout()`): The timeout for underlying socket read/write operations. While it applies strictly to socket activity, it is vital because it dictates how long the client will wait for long-running queries to return data. If your workload involves heavy analytical queries, you may need a very long socket timeout. However, the trade-off of a long socket timeout is the increased risk of encountering stale or silently dropped connections.
- **Socket timeout** (`Client.Builder.setSocketTimeout()`): The initial timeout for underlying socket read/write operations. While it applies strictly to socket activity, it is vital because it dictates how long the client will wait for long-running queries to return data. If your workload involves heavy analytical queries, you may need a very long socket timeout. However, the trade-off of a long socket timeout is the increased risk of encountering stale or silently dropped connections.
- **TCP keepalive**: Can be enabled to mitigate stale connections, though the host operating system's settings may ultimately override it. System-level TCP keepalive defaults are often several hours; configuring a shorter keepalive period makes sense for long-running operations. Keep in mind that executing extremely long operations over the public internet remains inherently risky.
- **Socket buffers** (`.setSocketRcvbuf()`, `.setSocketSndbuf()`): Not set by default, so the operating system sizes the socket buffers and auto-tunes them for the connection. Setting a fixed size turns that auto-tuning off and is additionally capped by the operating system limits, so a large value may have no effect. Configure these options only when a measurement shows a benefit for your workload.

> **Note on runtime configuration:** You can optionally override the default network timeout on a per-operation basis using `QuerySettings.setNetworkTimeout(long timeout, ChronoUnit unit)`. This allows you to set stricter boundaries on specific queries without altering the client-wide defaults.
> **Note on runtime configuration:** `Client.setNetworkTimeout(int milliseconds)` and `Client.setNetworkTimeout(long timeout, ChronoUnit unit)` change the client-wide timeout for subsequent requests; `0` disables it. Requests already created, including their retries, retain the timeout they started with. You can override the client value for one operation with `QuerySettings.setNetworkTimeout(long timeout, ChronoUnit unit)`.

### Ping - check connectivity

Expand Down
9 changes: 9 additions & 0 deletions history/latest/3260.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- **[client-v2, jdbc-v2]** Fixed `Connection#getNetworkTimeout()`, which returned `0` (no limit) when the `socket_timeout`
property was set and `setNetworkTimeout` was not called. A connection pool that saved this value and restored it
after validation disabled the configured socket timeout. The client now exposes a runtime network timeout, and
JDBC delegates `getNetworkTimeout()` and `setNetworkTimeout()` to it. Restoring the saved value therefore keeps
the configured timeout and applies it to subsequent requests from existing as well as new statements.
`setNetworkTimeout(executor, 0)` still disables the socket timeout. With a pool, the configured `socket_timeout`
now applies after validation: a request that takes longer fails with a socket timeout, and the connection is
closed because the pool set a network timeout.
(https://github.com/ClickHouse/clickhouse-java/issues/3260)
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import java.sql.Statement;
import java.sql.Struct;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -717,9 +716,9 @@ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLExc
// if timeout is set with this method then any timeout exception should be reported to the connection
// when connection get signal about timeout it uses executor to abort itself
// Some connection pools set timeout before calling Connection#close() to ensure that this operation will not hang
// Socket timeout is propagated with QuerySettings this connection has.
// The client keeps this as live connection-level state, so existing statements use the new value too.
networkTimeoutExecutor = executor;
defaultQuerySettings.setNetworkTimeout(milliseconds, ChronoUnit.MILLIS);
client.setNetworkTimeout(milliseconds);
}


Expand All @@ -743,7 +742,7 @@ public synchronized void onNetworkTimeout() {
@Override
public int getNetworkTimeout() throws SQLException {
ensureOpen();
return defaultQuerySettings.getNetworkTimeout().intValue();
return client.getNetworkTimeout();
}

/**
Expand Down
Loading
Loading