一次HttpClient连接泄露排查
在开发环境中,调试新功能时发现由于异常处理不严谨导致线程一直挂起的问题。
故障
在集成测试用,发现只要多次调用新开发的业务,就会出现前端卡死,后端响应线程一直挂起的情况,但没有任何报错。
查错
每一次重现的时候最后一条log都是定位到有些历史的HttpClient的工具类上,接口封装上游是直接一个大块try-catch并且没有做任何的异常处理,调用的下游才是直接对Apache HttpClient的封装,眨眼看上去并没有什么问题。
因为业务时异步并发调用接口,我在业务调用上改成timeout处理,断点发现有任务处于未完成状态,远程调用一直没有回调。然后我重新打开工具类,找到封装HttpClient调用的逻辑,搜索关于timeout的设置,如下:
RequestConfig
.custom()
.setSocketTimeout(socketTimeout)
.setConnectTimeout(connectTimeout)
.build();
然后看了一下文档,发现还有一个timeout的参数ConnectionRequestTimeout,然后我改动一下代码:
RequestConfig
.custom()
.setSocketTimeout(socketTimeout)
.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(requestTimeOut)
.build();
再次编译运行,问题依旧,但有了新发现,有新的异常捕获日志:
org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection
然后定位到调用下游逻辑块:
HttpResponse resp = client.execute(httpPost); //这里!
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = resp.getEntity();
respContent = EntityUtils.toString(httpEntity, "UTF-8");
}
这段代码中抛出了异常,给上游的调用捕获了,但是上下文中httpPost并没有做连接释放的处理,所以连接池中的连接越来越少。
解决
在逻辑中加上try-finally,显式调用释放请求连接。
try {
HttpResponse resp = client.execute(httpPost);
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = resp.getEntity();
respContent = EntityUtils.toString(httpEntity, "UTF-8");
EntityUtils.consume(httpEntity);
}
} finally {
httpPost.releaseConnection();
}