You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by rs...@apache.org on 2026/01/18 20:17:37 UTC

(httpcomponents-client) 02/02: DefaultManagedHttpClientConnection: Restore original socket timeout

This is an automated email from the ASF dual-hosted git repository.

rschmitt pushed a commit to branch 5.6.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 8fdd72edd8c5d081484b85dfdaa5aa7ba6e34708
Author: Ryan Schmitt <rs...@apache.org>
AuthorDate: Wed Jan 14 17:24:45 2026 -0800

    DefaultManagedHttpClientConnection: Restore original socket timeout
    
    This change fixes a bug in the synchronous client where socket timeouts
    were not being set correctly on reused connections, and the client was
    instead setting it to either the TLS handshake timeout or the
    `responseTimeout` from the `RequestConfig` of the previous request.
    
    The fix works by changing `DefaultManagedHttpClientConnection` to store
    the `socketTimeout` set on the socket at the time `bind` is called, and
    always restore that value when the `activate` method is called. Note
    that this requires the caller to call `setSoTimeout` on the socket
    _before_ binding the connection to it, hence the adjustments to some of
    the code in `DefaultHttpClientConnectionOperator`.
---
 .../testing/async/TestAsyncSocketTimeout.java      | 32 ++++++++++------------
 .../hc/client5/testing/sync/TestSocketTimeout.java | 16 +++++++----
 .../io/DefaultHttpClientConnectionOperator.java    |  4 +--
 .../io/DefaultManagedHttpClientConnection.java     |  1 -
 4 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncSocketTimeout.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncSocketTimeout.java
index ee17bb9cf..aae0ecb1b 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncSocketTimeout.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncSocketTimeout.java
@@ -38,7 +38,6 @@
 import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.util.VersionInfo;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Timeout;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -51,7 +50,6 @@
 import static org.apache.hc.core5.util.ReflectionUtils.determineJRELevel;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 abstract class AbstractTestSocketTimeout extends AbstractIntegrationTestBase {
@@ -83,21 +81,26 @@ void testReadTimeouts(final int connConfigTimeout, final int responseTimeout) th
         }
 
         for (final boolean drip : new boolean[]{ false, true }) {
-            final SimpleHttpRequest request = getRequest(responseTimeout, drip,
-                target);
-
-            final Throwable cause = assertThrows(ExecutionException.class, () -> client.execute(request, null).get())
-                .getCause();
-            assertInstanceOf(SocketTimeoutException.class, cause);
+            for (final boolean reuseConnection : new boolean[]{ false, true }) {
+                if (reuseConnection) {
+                    client.execute(getRequest(2500, 0, false, target), null).get();
+                }
+                final SimpleHttpRequest request = getRequest(responseTimeout, 2500, drip, target);
+
+                final Throwable cause = assertThrows(ExecutionException.class,
+                    () -> client.execute(request, null).get()).getCause();
+                assertInstanceOf(SocketTimeoutException.class, cause,
+                    String.format("drip=%s, reuseConnection=%s", drip, reuseConnection));
+            }
         }
 
         closeClient(client);
     }
 
-    private SimpleHttpRequest getRequest(final int responseTimeout, final boolean drip, final HttpHost target)
-        throws Exception {
+    private SimpleHttpRequest getRequest(final int responseTimeout, final int delay, final boolean drip,
+                                         final HttpHost target) throws Exception {
         final SimpleHttpRequest request = SimpleHttpRequest.create(Method.GET, target,
-            "/random/10240?delay=2500&drip=" + (drip ? 1 : 0));
+            "/random/10240?delay=" + delay + "&drip=" + (drip ? 1 : 0));
         if (responseTimeout > 0) {
             request.setConfig(RequestConfig.custom()
                 .setUnixDomainSocket(getUnixDomainSocket())
@@ -138,13 +141,6 @@ public Uds() {
         @Override
         void checkAssumptions() {
             assumeTrue(determineJRELevel() >= 16, "Async UDS requires Java 16+");
-            final String[] components = VersionInfo
-                .loadVersionInfo("org.apache.hc.core5", getClass().getClassLoader())
-                .getRelease()
-                .split("[-.]");
-            final int majorVersion = Integer.parseInt(components[0]);
-            final int minorVersion = Integer.parseInt(components[1]);
-            assumeFalse(majorVersion <= 5 && minorVersion <= 3, "Async UDS requires HttpCore 5.4+");
         }
     }
 }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestSocketTimeout.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestSocketTimeout.java
index 56bd63008..bb8caa2aa 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestSocketTimeout.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestSocketTimeout.java
@@ -85,15 +85,21 @@ void testReadTimeouts(final int socketConfigTimeout, final int connConfigTimeout
         }
 
         for (final boolean drip : new boolean[]{ false, true }) {
-            final HttpGet request = getRequest(responseTimeout, drip);
+            for (final boolean reuseConnection : new boolean[]{ false, true }) {
+                if (reuseConnection) {
+                    client.execute(target, getRequest(5000, 0, false), new BasicHttpClientResponseHandler());
+                }
+                final HttpGet request = getRequest(responseTimeout, 2500, drip);
 
-            assertThrows(SocketTimeoutException.class, () ->
-                client.execute(target, request, new BasicHttpClientResponseHandler()));
+                assertThrows(SocketTimeoutException.class, () ->
+                    client.execute(target, request, new BasicHttpClientResponseHandler()),
+                    String.format("drip=%s, reuseConnection=%s", drip, reuseConnection));
+            }
         }
     }
 
-    private HttpGet getRequest(final int responseTimeout, final boolean drip) throws Exception {
-        final HttpGet request = new HttpGet(new URI("/random/10240?delay=2500&drip=" + (drip ? 1 : 0)));
+    private HttpGet getRequest(final int responseTimeout, final int delay, final boolean drip) throws Exception {
+        final HttpGet request = new HttpGet(new URI("/random/10240?delay=" + delay + "&drip=" + (drip ? 1 : 0)));
         if (responseTimeout > 0) {
             request.setConfig(RequestConfig.custom()
                 .setUnixDomainSocket(getUnixDomainSocket())
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
index d1e31cddd..e0986ce9e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
@@ -261,8 +261,8 @@ private void upgradeToTls(final ManagedHttpClientConnection conn, final HttpHost
             socket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
         }
         final SSLSocket sslSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
-        conn.bind(sslSocket, socket);
         socket.setSoTimeout(soTimeout);
+        conn.bind(sslSocket, socket);
         onAfterTlsHandshake(context, endpointHost);
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(conn), tlsName);
@@ -287,8 +287,8 @@ private void connectToUnixDomainSocket(
             conn.bind(newSocket);
             final Socket socket = unixDomainSocketFactory.connectSocket(newSocket, unixDomainSocket,
                 connectTimeout);
-            conn.bind(socket);
             configureSocket(socket, socketConfig, false);
+            conn.bind(socket);
             onAfterSocketConnect(context, endpointHost);
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} {} connected to {}", ConnPoolSupport.getId(conn), endpointHost, unixDomainSocket);
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultManagedHttpClientConnection.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultManagedHttpClientConnection.java
index f5fb1f69f..1a9f27bba 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultManagedHttpClientConnection.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultManagedHttpClientConnection.java
@@ -166,7 +166,6 @@ public void setSocketTimeout(final Timeout timeout) {
             LOG.debug("{} set socket timeout to {}", this.id, timeout);
         }
         super.setSocketTimeout(timeout);
-        socketTimeout = timeout;
     }
 
     @Override