Skip to content

[JDBC] NetworkTimeout #2522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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 @@ -5,6 +5,7 @@
import com.clickhouse.client.api.ClientConfigProperties;
import com.clickhouse.client.api.enums.Protocol;
import com.clickhouse.client.api.internal.ValidationUtils;
import com.clickhouse.client.api.query.QuerySettings;
import org.apache.hc.core5.http.HttpHeaders;

import java.util.Collection;
Expand Down Expand Up @@ -264,4 +265,27 @@ public InsertSettings logComment(String logComment) {
public String getLogComment() {
return logComment;
}

public static InsertSettings merge(InsertSettings source, InsertSettings override) {
InsertSettings merged = new InsertSettings();
if (source != null) {
merged.rawSettings.putAll(source.rawSettings);
}
if (override != null && override != source) {// avoid copying the literally same object
merged.rawSettings.putAll(override.rawSettings);
}
return merged;
}

public void setNetworkTimeout(Long networkTimeout) {
if (networkTimeout != null) {
rawSettings.put(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), networkTimeout.intValue());
} else {
rawSettings.remove(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey());
}
}

public Long getNetworkTimeout() {
return (Long) rawSettings.get(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those methods are never used. Do you have any plans to add tests?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@ public String getLogComment() {
return logComment;
}

public void setNetworkTimeout(Long networkTimeout) {
if (networkTimeout != null) {
rawSettings.put(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), networkTimeout.intValue());
} else {
rawSettings.remove(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey());
}
}

public Long getNetworkTimeout() {
return (Long) rawSettings.get(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey());
}

public static QuerySettings merge(QuerySettings source, QuerySettings override) {
QuerySettings merged = new QuerySettings();
if (source != null) {
Expand Down
75 changes: 57 additions & 18 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.ClientConfigProperties;
import com.clickhouse.client.api.ClientException;
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
import com.clickhouse.client.api.internal.ServerSettings;
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.query.GenericRecord;
import com.clickhouse.client.api.query.QueryResponse;
import com.clickhouse.client.api.query.QuerySettings;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.data.ClickHouseFormat;
import com.clickhouse.jdbc.internal.ExceptionUtils;
import com.clickhouse.jdbc.internal.JdbcConfiguration;
import com.clickhouse.jdbc.internal.JdbcUtils;
Expand All @@ -16,6 +20,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
Expand Down Expand Up @@ -43,10 +48,11 @@
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class ConnectionImpl implements Connection, JdbcV2Wrapper {
private static final Logger log = LoggerFactory.getLogger(ConnectionImpl.class);
private static final Logger LOG = LoggerFactory.getLogger(ConnectionImpl.class);

protected final String url;
private final Client client; // this member is private to force using getClient()
Expand All @@ -65,9 +71,11 @@ public class ConnectionImpl implements Connection, JdbcV2Wrapper {

private final SqlParser sqlParser;

private Executor networkTimeoutExecutor;

public ConnectionImpl(String url, Properties info) throws SQLException {
try {
log.debug("Creating connection to {}", url);
LOG.debug("Creating connection to {}", url);
this.url = url;//Raw URL
this.config = new JdbcConfiguration(url, info);
this.onCluster = false;
Expand All @@ -86,10 +94,10 @@ public ConnectionImpl(String url, Properties info) throws SQLException {
}

if (this.config.isDisableFrameworkDetection()) {
log.debug("Framework detection is disabled.");
LOG.debug("Framework detection is disabled.");
} else {
String detectedFrameworks = Driver.FrameworksDetection.getFrameworksDetected();
log.debug("Detected frameworks: {}", detectedFrameworks);
LOG.debug("Detected frameworks: {}", detectedFrameworks);
if (!detectedFrameworks.trim().isEmpty()) {
clientName += " (" + detectedFrameworks + ")";
}
Expand Down Expand Up @@ -210,9 +218,8 @@ public void close() throws SQLException {
if (isClosed()) {
return;
}

client.close();
closed = true;
closed = true; // mark as closed to prevent further invocations
client.close(); // this will disrupt pending requests.
}

@Override
Expand Down Expand Up @@ -597,27 +604,59 @@ public String getSchema() throws SQLException {

@Override
public void abort(Executor executor) throws SQLException {
if (!config.isIgnoreUnsupportedRequests()) {
throw new SQLFeatureNotSupportedException("abort not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
if (executor == null) {
throw new SQLException("Executor must be not null");
}
// This method should check permissions with SecurityManager but the one is deprecated.
// There is no replacement for SecurityManger and it is marked for removal.
this.close();
}

@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
//TODO: Should this be supported?
if (!config.isIgnoreUnsupportedRequests()) {
throw new SQLFeatureNotSupportedException("setNetworkTimeout not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
ensureOpen();

// Very good mail thread about this method implementation. https://mail.openjdk.org/pipermail/jdbc-spec-discuss/2017-November/000236.html

// This method should check permissions with SecurityManager but the one is deprecated.
// There is no replacement for SecurityManger and it is marked for removal.
if (milliseconds > 0 && executor == null) {
// we need executor only for positive timeout values.
throw new SQLException("Executor must be not null");
}
if (milliseconds < 0) {
throw new SQLException("Timeout must be >= 0");
}

// How it should work:
// 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.
networkTimeoutExecutor = executor;
defaultQuerySettings.setOption(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), (long)milliseconds);
Copy link
Preview

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast to (long)milliseconds is inconsistent with the getNetworkTimeout() method which converts back to intValue(). Consider using consistent data types throughout.

Suggested change
defaultQuerySettings.setOption(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), (long)milliseconds);
defaultQuerySettings.setOption(ClientConfigProperties.SOCKET_OPERATION_TIMEOUT.getKey(), Integer.valueOf(milliseconds));

Copilot uses AI. Check for mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the new method setNetworkTimeout

}


// Should be called by child object to notify about timeout.
public void onNetworkTimeout() throws SQLException {
if (isClosed() || networkTimeoutExecutor == null) {
return; // we closed already so do nothing.
}

networkTimeoutExecutor.execute(() -> {
try {
this.abort(networkTimeoutExecutor);
} catch (SQLException e) {
throw new RuntimeException("Failed to abort connection", e);
}
});
}

@Override
public int getNetworkTimeout() throws SQLException {
//TODO: Should this be supported?
if (!config.isIgnoreUnsupportedRequests()) {
throw new SQLFeatureNotSupportedException("getNetworkTimeout not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
}

return -1;
Long networkTimeout = defaultQuerySettings.getNetworkTimeout();
return networkTimeout == null ? 0 : networkTimeout.intValue();
}

/**
Expand Down
4 changes: 4 additions & 0 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
Expand Down Expand Up @@ -99,6 +100,9 @@ public boolean next() throws SQLException {
try {
return reader.next() != null;
} catch (Exception e) {
if (e instanceof SocketTimeoutException) {
this.parentStatement.onNetworkTimeout();
}
throw ExceptionUtils.toSqlState(e);
}
}
Expand Down
13 changes: 13 additions & 0 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.SocketTimeoutException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
Expand Down Expand Up @@ -168,6 +169,10 @@ protected ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) thr
}
}
onResultSetClosed(null);

if (e instanceof SocketTimeoutException) {
this.connection.onNetworkTimeout();
}
throw ExceptionUtils.toSqlState(e);
}
}
Expand Down Expand Up @@ -203,6 +208,9 @@ protected long executeUpdateImpl(String sql, QuerySettings settings) throws SQLE
updateCount = Math.max(0, (int) response.getWrittenRows()); // when statement alters schema no result rows returned.
lastQueryId = response.getQueryId();
} catch (Exception e) {
if (e instanceof SocketTimeoutException) {
this.connection.onNetworkTimeout();
}
throw ExceptionUtils.toSqlState(e);
}

Expand Down Expand Up @@ -610,4 +618,9 @@ public long executeLargeUpdate(String sql, String[] columnNames) throws SQLExcep
public String getLastQueryId() {
return lastQueryId;
}

// Proxy method for child objects. Do not call.
public void onNetworkTimeout() throws SQLException {
Comment on lines +622 to +623
Copy link
Preview

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment 'Do not call' is unclear and potentially confusing. Consider explaining when and how this method should be used, or make it package-private if it's only for internal use.

Suggested change
// Proxy method for child objects. Do not call.
public void onNetworkTimeout() throws SQLException {
/**
* Proxy method for use by child objects within the package to handle network timeouts.
*/
void onNetworkTimeout() throws SQLException {

Copilot uses AI. Check for mistakes.

this.connection.onNetworkTimeout();
}
}
10 changes: 10 additions & 0 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/WriterStatementImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
Expand Down Expand Up @@ -109,6 +110,9 @@ public long executeLargeUpdate() throws SQLException {
try {
writer.commitRow();
} catch (Exception e) {
if (e instanceof SocketTimeoutException) {
this.connection.onNetworkTimeout();
}
throw new SQLException(e);
}

Expand All @@ -121,6 +125,9 @@ public long executeLargeUpdate() throws SQLException {
updateCount = Math.max(0, (int) response.getWrittenRows()); // when statement alters schema no result rows returned.
lastQueryId = response.getQueryId();
} catch (Exception e) {
if (e instanceof SocketTimeoutException) {
this.connection.onNetworkTimeout();
}
throw ExceptionUtils.toSqlState(e);
} finally {
try {
Expand Down Expand Up @@ -298,6 +305,9 @@ public void addBatch() throws SQLException {
try {
writer.commitRow();
} catch (Exception e) {
if (e instanceof SocketTimeoutException) {
this.connection.onNetworkTimeout();
}
throw new SQLException(e);
}
}
Expand Down
11 changes: 11 additions & 0 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.clickhouse.data.Tuple;
import com.clickhouse.jdbc.types.Array;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;

import java.awt.*;
import java.math.BigInteger;
Expand Down Expand Up @@ -297,4 +298,14 @@ public static List<Object> convertList(List<Object> values, Class<?> type) throw
}
return convertedValues;
}

public static void safeClose(AutoCloseable closeable, Logger logger) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception ex) {
logger.warn("Failed to close closeable after exception", ex);
}
}
}
}
26 changes: 16 additions & 10 deletions jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
import java.util.Base64;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

public class ConnectionTest extends JdbcIntegrationTest {
Expand Down Expand Up @@ -384,20 +387,23 @@ public void setSchemaTest() throws SQLException {

@Test(groups = { "integration" })
public void abortTest() throws SQLException {
Connection localConnection = this.getJdbcConnection();
assertThrows(SQLFeatureNotSupportedException.class, () -> localConnection.abort(null));
try (Connection conn = this.getJdbcConnection()) {
conn.abort(Executors.newSingleThreadExecutor());
assertTrue(conn.isClosed());
}
}

@Test(groups = { "integration" })
public void setNetworkTimeoutTest() throws SQLException {
Connection localConnection = this.getJdbcConnection();
assertThrows(SQLFeatureNotSupportedException.class, () -> localConnection.setNetworkTimeout(null, 0));
}
public void testNetworkTimeout() throws SQLException {
try {
Connection conn = this.getJdbcConnection();
int t1 = (int) TimeUnit.SECONDS.toMillis(20);
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), t1);
Assert.assertEquals(t1, conn.getNetworkTimeout());

@Test(groups = { "integration" })
public void getNetworkTimeoutTest() throws SQLException {
Connection localConnection = this.getJdbcConnection();
assertThrows(SQLFeatureNotSupportedException.class, localConnection::getNetworkTimeout);
} catch (Exception e) {

}
}

@Test(groups = { "integration" })
Expand Down
Loading