Spring中Controller执行无限循环后未返回响应的排查求助
问题分析与解决思路
代码逻辑说明
接口整体流程为:Controller接收请求后,调用业务处理方法,其中包含一个最长耗时数小时的阻塞循环(当前场景耗时15分钟),循环结束后回到Controller打印日志,再返回响应。但部分请求出现日志已打印、但响应无法返回的情况,客户端(Postman)一直显示"Sending request..."。
Controller.java 接口代码
@GetMapping("/endpoint") public ResponseEntity<?> addDocument(@RequestParam String param1, @RequestParam String param2) { AddDocumentRequest request = new AddDocumentRequest(param1, param2); callRequestHandler(request); log.info("Job Done"); return ResponseEntity.ok(true); }
Controller.java 中 callRequestHandler 方法
private void callRequestHandler(AddDocumentRequest request) { try { requestHandler.requestHandler(request); } catch (Exception e) { log.error("error"); } }
RequestHandler.java(@Service类)中 requestHandler 方法
public void requestHandler(AddDocumentRequest request) throws Exception { // 调用@Async方法 CompletableFuture<String> registry = service.addDocuments(request); // 阻塞线程等待目标状态 blockAndWaitResponses(request); }
阻塞循环方法 blockAndWaitResponses
private void blockAndWaitResponses(AddDocumentRequest request) throws Exception { while (true) { // 忙等待 Thread.sleep(120000); // 2分钟 if (desiredStateOccured()) { insertDataToDB(); break; } else { updateState(); } } }
可能的原因排查方向
- Tomcat工作线程池耗尽:长时间阻塞的请求会占用Tomcat工作线程,当并发请求超过
maxThreads配置时,后续请求会进入等待队列。若队列满(acceptCount),新请求会被拒绝;即使日志已打印,也需确认线程是否真的释放,可通过JVM线程栈(jstack)查看当前工作线程状态。 - 响应发送阶段阻塞:Spring MVC在返回响应时,可能被自定义过滤器/拦截器、或Tomcat的响应缓冲区配置卡住。比如某些Filter在
afterCompletion阶段执行耗时操作,或缓冲区未及时刷新。 - 隐性资源未释放:
insertDataToDB()虽未抛出异常,但可能存在数据库连接未释放、事务未提交等情况,间接导致线程无法正常完成响应流程。可在该方法末尾添加日志,确认执行完成时间点。 - 网络/客户端超时:Postman或中间代理(如网关、Nginx)的超时设置可能导致客户端提前断开连接,但服务端仍在处理。可换用curl等工具测试,对比结果。
- Spring请求上下文异常:长时间阻塞后,请求上下文(如
RequestContextHolder)可能被意外清理,导致Spring无法正确组装响应。
解决建议
- 监控线程状态:出现问题时,用
jstack <pid>导出线程栈,查看执行完log.info("Job Done")的线程是否卡在org.apache.catalina.connector.CoyoteAdapter.service等响应发送相关方法上。 - 排查过滤器/拦截器:暂时禁用自定义Filter/Interceptor,测试是否能正常返回响应,逐步定位问题点。
- 添加响应阶段日志:通过Spring的
HandlerInterceptor在postHandle和afterCompletion阶段添加日志,确认响应处理的各个时间节点。 - 调整Tomcat配置:适当增大
maxThreads缓解线程池压力,同时设置connection-timeout和keep-alive-timeout,避免无效连接占用资源。 - 验证数据库操作:在
insertDataToDB()中添加详细日志,确认事务提交、连接释放的时间,排除隐性阻塞。
内容的提问来源于stack exchange,提问作者user275616
相关产品推荐
相关产品推荐

