升级至Spring Boot 3.0.0(Apache HttpClient 5)遇DeadlineTimeoutException
问题
项目迁移至Java 17/Spring Boot 3.0.0后,部分集成测试失败。示例测试代码如下:
@Test void getMessageInfos_Success_WithNonDefaultPeriodSent() throws Exception { var token = createToken(); var ciUrlMessages = format("/%s/%s/messages;directions=%s;period=%s;maxCount=2000\?fetchText=false", CONTEXT, MAC, Direction.IN, 10); var ciUrlRecipients = format("/%s/%s/possible-recipients", CONTEXT, MAC); stub(GET, ciUrlMessages, okJson(loadJsonFromFile("/__files/inbox/inbox_standard_incomming_messages_without_text.json"))); stub(GET, ciUrlRecipients, okJson(loadJsonFromFile("/__files/inbox/possible_recipients_only_agency.json"))); mvc.perform(post(INBOX_MESSAGES_INFO_URL, CONTEXT, MAC) .with(x509(CLIENT_CERT)) .header(AUTHORIZATION, BEARER + token) .content(createMessageInfoRequestJsonForPeriodTests(true, 10)) .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.[0].messageId", is("2943405"))) .andExpect(jsonPath("$.[1].messageId", is("2899350"))) .andExpect(jsonPath("$.[2].messageId", is("2896472"))) .andExpect(jsonPath("$.[3].messageId", is("2855248"))); verify(getRequestedFor(urlEqualTo(format("/%s/%s/messages;directions=%s;period=%s;maxCount=2000?fetchText=false", CONTEXT, MAC, Direction.IN, 10)))); verify(getRequestedFor(urlEqualTo(ciUrlRecipients))); }
调试时出现的异常栈:
java.util.concurrent.ExecutionException: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:56789/VjI7MTQ4NzU4NDE1NjAxNTs2MjE7QU9TOzI1MTE5ODA7MTI1NzMwODk5Mzs9O0F-OzI1MTE5ODI7RX47MjUxMTk4MQ/kzitRN6RuUabptLwyOx2y5-dE0A/messages;directions=IN;period=10;maxCount=2000": Request execution failed at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396) at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096)
根源异常:
Caused by: org.apache.hc.core5.util.DeadlineTimeoutException: Deadline: 292278994-08-17T09:12:55.807+0200, 9223370335509990168 MILLISECONDS overdue at org.apache.hc.core5.util.DeadlineTimeoutException.from(DeadlineTimeoutException.java:49) at org.apache.hc.core5.pool.StrictConnPool.lease(StrictConnPool.java:217) 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) ... 46 common frames omitted
此前使用Spring Boot 2.7.x(对应HttpClient 4)时,将connectionRequestTimeout、connectTimeout、SocketTimeout设为-1实现无超时。迁移到Apache HttpClient 5后,按文档将这些值设为0,但调试发现StrictConnPool.lease方法中执行acquiredLock = this.lock.tryLock(requestTimeout.getDuration(), requestTimeout.getTimeUnit());时,因requestTimeout.getDuration()为0,导致无法获取锁,抛出超时异常。
询问:若要实现无超时限制,requestTimeout应设置为何值?是否应在HttpConfiguration中将connectionRequestTimeout、connectTimeout、SocketTimeout设为Timeout.DISABLED?
解决方案
- 核心结论:要实现无超时限制,必须使用
Timeout.DISABLED而非数值0。 - 原因分析:在Apache HttpClient 5中,数值0并非代表无超时,而是会被解析为立即超时(对应
tryLock(0, timeUnit)会直接尝试获取锁,失败则立即抛出超时异常),这就是触发DeadlineTimeoutException的原因。 - 具体配置方式:
在配置HttpClient时,将connectionRequestTimeout、connectTimeout、socketTimeout都设置为Timeout.DISABLED,示例代码如下:
若使用Spring Boot自动配置,可通过配置文件或自定义HttpClient httpClient = HttpClientBuilder.create() .setConnectionTimeToLive(Timeout.DISABLED) .setDefaultRequestConfig(RequestConfig.custom() .setConnectTimeout(Timeout.DISABLED) .setConnectionRequestTimeout(Timeout.DISABLED) .setSocketTimeout(Timeout.DISABLED) .build()) .build();RestTemplate/HttpClientBean来设置这些参数,确保所有超时参数都使用Timeout.DISABLED而非数值0或-1。 - 验证逻辑:当使用
Timeout.DISABLED时,requestTimeout.getDuration()会返回代表无限制的特殊值,此时tryLock方法会转为阻塞式获取锁(即调用lock()而非带超时的tryLock(...)),不会因超时导致锁获取失败。
内容的提问来源于stack exchange,提问作者Ivajlo Iliev
相关产品推荐
相关产品推荐

