On Thu, 2025-07-31 at 10:15 +0200, Cenk Pekyaman wrote:
> HttpRequestConfig : our POJO containing timeout settings and
> connection
> pool settings.
> 
> connectionManager (PoolingAsyncClientConnectionManagerBuilder):
> ```
> connectionManagerBuilder.setConnectionConfigResolver(
>    (route) -> ConnectionConfig.custom()
>                        // 30 seconds
> 
> .setTimeToLive(TimeValue.of(HttpClientConfigDefaults.DEFAULT_CONNECTI
> ON_TTL))
>                        // default 2 seconds for most clients
> 
>  .setConnectTimeout(Timeout.of(httpRequestConfig.getConnectTimeout())
> )
>                        .build()
> );
> // force http 1.1 currently
> connectionManagerBuilder.setDefaultTlsConfig(TlsConfig.custom().setVe
> rsionPolicy(getHttpVersionPolicy(httpRequestConfig)).build());
> // sslContext with some trustStore setup and a custom
> hostnameVerifier
> connectionManagerBuilder.setTlsStrategy(ClientTlsStrategyBuilder.crea
> te().setSslContext(sslContext).setHostnameVerifier(hostnameVerifier).
> build());
> ```
> 
> IOReactor:
> ```
> IOReactorConfig ioReactorConfig =
>                 IOReactorConfig.custom()
>                          // 30 seconds default
> 
> .setSoTimeout(Timeout.of(HttpClientConfigDefaults.DEFAULT_READ_TIMEOU
> T))
>                         .build();
> ```
> 
> CloseableHttpAsyncClient (clients are cached essentially per
> httpRequestConfig)
> ```
> RequestConfig defaultRequestConfig =
> RequestConfig.custom().setCookieSpec(StandardCookieSpec.RELAXED).buil
> d();
> 
> HttpAsyncClientBuilder.create()
>   .disableAutomaticRetries()
>   .setConnectionManagerShared(true)
>   .setDefaultRequestConfig(defaultRequestConfig)
>   // from above
>   .setIOReactorConfig(ioReactorConfig)
>   // from above
>   .setConnectionManager(connectionManager)
>   .build();
> ```
> 
> our requestTimeout configuration (HttpClientContext):
> ```
> HttpClientContext httpClientContext = new HttpClientContext();
> var requestConfigBuilder = RequestConfig.custom()
>    // 10 seconds for this particular case
>   
> .setResponseTimeout(Timeout.of(httpRequestConfig.getSocketTimeout()))
>    // default 2 seconds (set from connectTimeout by default)
> 
>  .setConnectionRequestTimeout(Timeout.of(httpRequestConfig.getConnect
> ionRequestTimeout()));
> httpClientContext.setRequestConfig(requestConfigBuilder.build());
> ```
> 
> request call (async with callback):
> ```
> // the last parameter is our class implementing FutureCallback
> Future<Message<HttpResponse, RES>> clientFuture =
> asyncHttpClient.execute(asyncRequestProducer, responseConsumer, null,
> httpClientContext, this);
> // callTimeout is essentially connectTimeout + socketTimeout by
> default.
> return clientFuture.get(callTimeout, TimeUnit.MILLISECONDS);
> ```
> 
> stack trace (when this specific timeout happens):
> ```
>  at
> org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:16
> 6)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.concurrent.ComplexFuture.failed(ComplexFuture.jav
> a:79)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient
> $2.failed(InternalAbstractHttpAsyncClient.java:364)
> [httpclient5-5.4.4.jar:5.4.4]
> at
> org.apache.hc.client5.http.impl.async.AsyncRedirectExec$1.failed(Asyn
> cRedirectExec.java:246)
> [httpclient5-5.4.4.jar:5.4.4]
> at
> org.apache.hc.client5.http.impl.async.AsyncProtocolExec$1.failed(Asyn
> cProtocolExec.java:295)
> [httpclient5-5.4.4.jar:5.4.4]
> at
> org.apache.hc.client5.http.impl.async.AsyncConnectExec$2.failed(Async
> ConnectExec.java:233)
> [httpclient5-5.4.4.jar:5.4.4]
> at
> org.apache.hc.core5.concurrent.CallbackContribution.failed(CallbackCo
> ntribution.java:52)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:16
> 6)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.concurrent.ComplexFuture.failed(ComplexFuture.jav
> a:79)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManag
> er$4.failed(PoolingAsyncClientConnectionManager.java:482)
> [httpclient5-5.4.4.jar:5.4.4]
> at
> org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:16
> 6)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.concurrent.ComplexFuture.failed(ComplexFuture.jav
> a:79)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.concurrent.FutureContribution.failed(FutureContri
> bution.java:52)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.concurrent.CallbackContribution.failed(CallbackCo
> ntribution.java:52)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.ssl.SSLIOSession$1.exception(SSLIOSession
> .java:233)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.ssl.SSLIOSession$1.timeout(SSLIOSession.j
> ava:223)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.InternalDataChannel.onTimeout(InternalDat
> aChannel.java:170)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.InternalChannel.checkTimeout(InternalChan
> nel.java:67)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.SingleCoreIOReactor.checkTimeout(SingleCo
> reIOReactor.java:239)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.SingleCoreIOReactor.validateActiveChannel
> s(SingleCoreIOReactor.java:166)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreI
> OReactor.java:128)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(Abstr
> actSingleCoreIOReactor.java:92)
> [httpcore5-5.3.4.jar:5.3.4]
> at
> org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:
> 44)
> [httpcore5-5.3.4.jar:5.3.4]
> at java.lang.Thread.run(Thread.java:840) [?:?]
> Caused by: java.net.SocketTimeoutException: 2 SECONDS
> at
> org.apache.hc.core5.io.SocketTimeoutExceptionFactory.create(SocketTim
> eoutExceptionFactory.java:50)
> ~[httpcore5-5.3.4.jar:5.3.4]
> ```
> 
> I hope the details are enough.
> 
> Thanks.
> 

This looks like a bug (or at least inconsistency between the classic
and the async transports). The former uses socket timeout as a default
whereas the latter uses the connect timeout. 

Please raise a JIRA ticket for this issue.

Oleg


> On Wed, Jul 30, 2025 at 8:14 PM Oleg Kalnichevski <[email protected]>
> wrote:
> 
> > On Wed, 2025-07-30 at 12:54 +0200, Cenk Pekyaman wrote:
> > > httpclient5 version: 5.4.4
> > > httpCore5 version: 5.3.4
> > > Java version: 17 (21 since recently)
> > > 
> > > We are using http-client-5 under our api-client layer with async
> > > +
> > > TLS.
> > > Once in a while, we get a timeout exception like this:
> > > "java.net.SocketTimeoutException: 2 SECONDS" in some requests.
> > > and
> > > from the
> > > stack trace, we see that the exception is coming from
> > > "InternalDataChannel.onTimeout". but the value we see is actually
> > > the
> > > connectTimeout, not the readTimeout(socketTimeout) we set for the
> > > request.
> > > 
> > > The explanation we came up with is that, the client completes the
> > > initial
> > > connect step with "InternalConnectChannel" and then the socket is
> > > handed
> > > over to "InternalDataChannel" to do the TLS handshake in
> > > "startTls",
> > > which
> > > either takes too long to complete, or hangs. We came to this
> > > conclusion
> > > because the code flow seems to have TLS handshake after connect
> > > in
> > > sessionRequest.complete callback and handshake uses
> > > connectTimeout by
> > > default (and also our request traces don't show a request
> > > reaching
> > > the
> > > server for such cases).
> > > 
> > > Our question(s):
> > > 1. Is that really what is happening here.
> > 
> > I cannot say. I need more context / details
> > 
> > > 2. Is there anything specific we might check to find the root
> > > cause.
> > 
> > I need to see exactly how timeouts get configured in your code and
> > I
> > need to see the exact exception stack trace to be able to tell
> > more.
> > 
> > > 3. if it is a "tls handshake too long" issue, can http-client
> > > throw a
> > > specific exception for that.
> > > 
> > 
> > I think we could do that.
> > 
> > Oleg
> > 
> > -------------------------------------------------------------------
> > --
> > To unsubscribe, e-mail: [email protected]
> > For additional commands, e-mail:
> > [email protected]
> > 
> > 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to