Jetty HttpClient触发SIGTERM时抛出AsynchronousCloseException问题排查
解决Jetty HttpClient优雅停机时的AsynchronousCloseException问题
我之前在做Jetty HttpClient优雅停机的时候,也踩过这个AsynchronousCloseException的坑,结合实战经验给你梳理下问题根源和具体解决步骤:
问题根源
当你用kill命令触发ShutdownHook时,如果直接粗暴调用HttpClient的停止方法,底层的NIO通道会被强制关闭,而此时还有正在处理的异步请求在进行IO操作,就会抛出这个异常。本质是停机流程没有给正在执行的请求留足完成时间,也没有正确区分“正常停机关闭”和“异常错误”。
具体解决办法
1. 先拦截新请求,避免后续任务涌入
先维护一个全局的停机状态标记,在提交任何新请求前做检查,确保停机过程中不再接收新任务:
private static final AtomicBoolean IS_SHUTTING_DOWN = new AtomicBoolean(false); // 提交请求前先判断 if (IS_SHUTTING_DOWN.get()) { throw new IllegalStateException("System is shutting down, cannot accept new requests"); } httpClient.send(request, responseListener);
2. 优化ShutdownHook中的停机流程
不要直接调用httpClient.stop(),而是按顺序关闭组件,给正在处理的请求留足完成时间:
Runtime.getRuntime().addShutdownHook(new Thread(() -> { IS_SHUTTING_DOWN.set(true); LOGGER.info("Starting graceful shutdown of Jetty HttpClient..."); try { // 先关闭执行器,停止接受新任务 httpClient.getExecutor().shutdown(); // 关闭调度器 httpClient.getScheduler().shutdown(); // 设置超时时间,比如30秒,让正在处理的请求完成 long timeout = 30; // 优雅停止HttpClient httpClient.stop(timeout, TimeUnit.SECONDS); // 等待执行器终止,超时则强制关闭 if (!httpClient.getExecutor().awaitTermination(timeout, TimeUnit.SECONDS)) { LOGGER.warn("Executor did not terminate in time, forcing shutdown"); httpClient.getExecutor().shutdownNow(); } LOGGER.info("Jetty HttpClient gracefully shut down"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); LOGGER.error("Graceful shutdown interrupted", e); } catch (Exception e) { LOGGER.error("Failed to gracefully shut down HttpClient", e); } }));
3. 在响应Listener中区分异常类型
在你注册的请求/响应Listener里,捕获AsynchronousCloseException,判断是否是停机导致的,避免误报:
Response.CompleteListener responseListener = new Response.CompleteListener() { @Override public void onComplete(Result result) { if (result.isFailed()) { Throwable failure = result.getFailure(); if (failure instanceof AsynchronousCloseException && IS_SHUTTING_DOWN.get()) { // 这是停机过程中的正常关闭,无需告警 LOGGER.info("Request closed during graceful shutdown: {}", result.getRequest().getURI()); } else { // 处理真正的业务异常 LOGGER.error("Request failed: {}", result.getRequest().getURI(), failure); } } else { // 正常处理响应 LOGGER.info("Request succeeded: {}", result.getRequest().getURI()); } } };
4. 调整HttpClient的连接池配置
合理配置连接池参数,减少停机时的资源占用和异常概率:
HttpClient httpClient = new HttpClient(); // 设置连接空闲超时,避免空闲连接占用资源 httpClient.setIdleTimeout(5000); // 配置连接池 ConnectionPool pool = new QueuedThreadPoolConnectionPool(); pool.setMaxConnectionsPerDestination(100); pool.setIdleTimeout(5000); httpClient.setConnectionPool(pool);
测试验证
测试的时候可以模拟10-20个并发请求,然后执行kill -TERM <进程ID>,观察日志:
- 新请求会被拦截
- 正在处理的请求能正常完成
- AsynchronousCloseException被正确识别为正常停机事件,不会触发错误告警
内容的提问来源于stack exchange,提问作者douglaslps
相关产品推荐
相关产品推荐

