Apache HttpClient 5.2连接池超时异常问题咨询
我们正从Apache HttpClient 4.5升级至5.2版本,发现连接池超时机制发生重大变化。我们希望配置固定数量的可用连接,额外线程无需等待。由于非正值会被视为无限超时,因此将ConnectionRequestTimeout设置为1ms,以便连接池耗尽时立即检测并失败。
在4.5版本中该方案运行正常,但在5.2版本中,超时检查似乎提前触发,即使连接池仍有可用连接也会报错。以下是可稳定复现问题的测试代码:
class Test { static CloseableHttpClient client; public static void main(String[] args) { var requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(Timeout.ofMilliseconds(1)) .build(); var connManager = new PoolingHttpClientConnectionManager(); connManager.setMaxTotal(50); connManager.setDefaultMaxPerRoute(50); client = HttpClients.custom() .setDefaultRequestConfig(requestConfig) .setConnectionManager(connManager) .build(); for (int i = 0; i < 45; i++) { new Requester().start(); } } static class Requester extends Thread { String result; @Override public void run() { try { result = client.execute(new HttpGet("https://www.example.com"), ClassicHttpResponse::toString); } catch (IOException e) { e.printStackTrace(); } } } }
运行后会输出如下堆栈信息:
org.apache.hc.client5.http.impl.classic.RequestFailedException: Request execution failed at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.acquireEndpoint(InternalExecRuntime.java:131) at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:125) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:113) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:116) at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51) at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170) at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:245) at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:188) at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:162) at Test$Requester.run(Test.java:40) Caused by: org.apache.hc.core5.util.DeadlineTimeoutException: Deadline: 2024-01-10T02:02:37.621+0000, -15 MILLISECONDS overdue at org.apache.hc.core5.util.DeadlineTimeoutException.from(DeadlineTimeoutException.java:49) at org.apache.hc.core5.pool.StrictConnPool.lease(StrictConnPool.java:222) at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager.lease(PoolingHttpClientConnectionManager.java:298) at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.acquireEndpoint(InternalExecRuntime.java:103) ... 15 more
将超时时间增加至25ms可降低超时频率,但仍会偶尔出现问题。请问这是bug吗?是否有其他方式配置低超时或无超时的连接池?
这不是Bug,是HttpClient 5.x的设计变更
HttpClient 5.x对连接池的超时计算逻辑做了调整:在4.x中,ConnectionRequestTimeout是线程等待连接的最长时间;但在5.x中,这个超时是从请求开始到获取到连接的总时间窗口,包括请求调度、池内锁竞争、连接创建(如果需要)等所有前置耗时。即使池内有空闲连接,线程调度、锁获取的微小延迟也可能耗尽1ms的超时窗口,触发超时异常。
替代方案
若要实现「连接池耗尽时立即失败,不等待」的需求,可通过以下方式配置:
利用5.x的超时语义直接配置
在HttpClient 5.x中,Timeout.ofMilliseconds(0)代表「立即超时,不等待」,而非无限等待(无限等待为Timeout.ofMilliseconds(-1))。只需将ConnectionRequestTimeout设置为0ms,配合已配置的最大连接数限制,就能实现连接池耗尽时直接拒绝请求的效果。修改后的请求配置代码:var requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(Timeout.ofMilliseconds(0)) .build();确认连接池策略
HttpClient 5.x默认使用StrictConnPool,当池内无空闲连接且已达最大限制时,会直接触发超时(若设置了0ms超时则立即失败),无需额外调整连接池策略。
额外说明
- 避免设置极小的超时值(如1ms),正常的线程调度和锁操作都可能超过这个时间,导致误判。
- 明确5.x中
Timeout类的语义:Timeout.ofMilliseconds(-1):无限等待Timeout.ofMilliseconds(0):立即超时,不等待- 正数:实际等待时间窗口
内容的提问来源于stack exchange,提问作者shmosel

