这类问题我又碰上了一次。Spring Boot 3.1 用 SseEmitter 推流,前端每隔 30 秒收到一次 onerror,服务端没有打印异常,容器是内嵌 Tomcat 10.1,接口里也没有显式设置超时。
第一反应是「心跳在业务线程里发 被线程池排队拖过超时」,验证下来不成立。
原因不止一个,得分开看。SseEmitter 默认超时取容器 async 超时 30 秒;没有注册 onTimeout 回调 连接被直接回收;反向代理的读超时同样是 30 秒。
构造时把超时显式写成 10 分钟
grep -n 'new SseEmitter' src/main/java/StreamController.java
注册 onTimeout 与 onCompletion 打日志确认触发点
grep -n onTimeout src/main/java/StreamController.java
接着用 curl 观察连接存活时间
curl -N -v http://localhost:8080/stream
同步调大 Tomcat 的 async 超时
grep -n spring.mvc.async src/main/resources/application.yml
我习惯把每一步的执行时间和结果记在一边,后面复盘省事。
改之前我把相关日志按时间截了一段存着,事后对时间线用得上。
怎么算真的好了,看的是同一批现象。连接能稳定保持 10 分钟以上,onTimeout 只在客户端主动断开时触发,curl 输出里没有提前结束。
补一句。SseEmitter 的超时和容器 async 超时是两层,只改一处仍然会断,排查时把两边的值都打出来。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。
