diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java index 9289833a3..de00f10e4 100644 --- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java +++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ApacheHttpConnectionImpl.java @@ -376,6 +376,7 @@ public HttpConnectionManager(Registry socketFactory, Cl ConnectionConfig connConfig = ConnectionConfig.custom() .setConnectTimeout(Timeout.of(config.getConnectionTimeout(), TimeUnit.MILLISECONDS)) + .setValidateAfterInactivity(config.getLongOption(ClickHouseHttpOption.AHC_VALIDATE_AFTER_INACTIVITY), TimeUnit.MILLISECONDS) .build(); setDefaultConnectionConfig(connConfig); diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java index 1dbb6e4b3..f24502f90 100644 --- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java +++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java @@ -64,7 +64,16 @@ public enum ClickHouseHttpOption implements ClickHouseOption { * Only one role can be set at a time. */ REMEMBER_LAST_SET_ROLES("remember_last_set_roles", false, - "Whether to remember last set role and send them in every next requests as query parameters."); + "Whether to remember last set role and send them in every next requests as query parameters."), + + /** + * The time in milliseconds after which the connection is validated after inactivity. + * Default value is 5000 ms. If set to negative value, the connection is never validated. + * It is used only for Apache Http Client connection provider. + */ + AHC_VALIDATE_AFTER_INACTIVITY("ahc_validate_after_inactivity", 5000L, + "The time in milliseconds after which the connection is validated after inactivity."), + ; private final String key; private final Serializable defaultValue; diff --git a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java index 1ba326a46..220745409 100644 --- a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java +++ b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.Serializable; +import java.net.ConnectException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -25,9 +26,13 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.http.Fault; +import com.github.tomakehurst.wiremock.stubbing.Scenario; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.core5.http.NoHttpResponseException; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class ApacheHttpConnectionImplTest extends ClickHouseHttpClientTest { @@ -144,4 +149,63 @@ public void testFailureWhileRequest() { faultyServer.stop(); } } + + @Test(groups = {"unit"}, dataProvider = "validationTimeoutProvider") + public void testNoHttpResponseExceptionWithValidation(long validationTimeout) { + + faultyServer = new WireMockServer(9090); + faultyServer.start(); + + faultyServer.addStubMapping(WireMock.post(WireMock.anyUrl()) + .inScenario("validateOnStaleConnection") + .withRequestBody(WireMock.equalTo("SELECT 100")) + .willReturn(WireMock.aResponse() + .withHeader("X-ClickHouse-Summary", + "{ \"read_bytes\": \"10\", \"read_rows\": \"1\"}")) + .build()); + + + ClickHouseHttpClient httpClient = new ClickHouseHttpClient(); + Map options = new HashMap<>(); + options.put(ClickHouseHttpOption.AHC_VALIDATE_AFTER_INACTIVITY, validationTimeout); + options.put(ClickHouseHttpOption.MAX_OPEN_CONNECTIONS, 1); + ClickHouseConfig config = new ClickHouseConfig(options); + httpClient.init(config); + ClickHouseRequest request = httpClient.read("http://localhost:9090/").query("SELECT 100"); + + Runnable powerBlink = () -> { + try { + Thread.sleep(100); + faultyServer.stop(); + Thread.sleep(50); + faultyServer.start(); + } catch (InterruptedException e) { + Assert.fail("Unexpected exception", e); + } + }; + try { + ClickHouseResponse response = httpClient.executeAndWait(request); + Assert.assertEquals(response.getSummary().getReadRows(), 1); + response.close(); + new Thread(powerBlink).start(); + Thread.sleep(200); + response = httpClient.executeAndWait(request); + Assert.assertEquals(response.getSummary().getReadRows(), 1); + response.close(); + } catch (Exception e) { + if (validationTimeout < 0) { + Assert.assertTrue(e instanceof ClickHouseException); + Assert.assertTrue(e.getCause() instanceof ConnectException); + } else { + Assert.fail("Unexpected exception", e); + } + } finally { + faultyServer.stop(); + } + } + + @DataProvider(name = "validationTimeoutProvider") + public static Object[] validationTimeoutProvider() { + return new Long[] {-1L , 100L }; + } }