首页/软件开发/后端开发/后端挂起调试/
名称:后端挂起调试
描述:诊断并修复由新闻流路由中阻塞的ThreadPoolExecutor关闭引起的FastAPI挂起问题;包含py-spy捕获和非阻塞执行器模式。
后端挂起调试
目的
- 检测并解决由于SSE新闻流中同步执行器关闭导致的事件循环挂起(例如
curl http://localhost:8000/超时)。
- 提供使用
py-spy捕获实时堆栈并精确定位阻塞代码的可重复排查流程。
范围
- 后端:
backend/app/api/routes/stream.py(新闻流)、backend/app/services/rss_ingestion.py(RSS工作线程)、启动过程。
- 工具:
py-spy用于实时堆栈转储;带超时的curl用于冒烟测试。
快速排查
- 复现挂起:
curl -m 5 http://localhost:8000/和curl -m 5 http://localhost:8000/health;记录超时情况。
- 进程检查:
ss -tlnp | grep 8000确认监听器;ls /proc/$(pgrep -f "uvicorn app.main")/fd | wc -l排除文件描述符泄漏。
- 堆栈捕获(后端虚拟环境内):
uv pip install py-spy然后sudo /home/bender/classwork/Thesis/backend/.venv/bin/py-spy dump --pid $(pgrep -f "uvicorn app.main")(多进程时包括工作进程pid)。查找api/routes/stream.py帧中的ThreadPoolExecutor.shutdown。
修复模式(非阻塞执行器)
- 将
event_generator内的同步上下文管理器with ThreadPoolExecutor(...):替换为长生命周期执行器加显式非阻塞关闭:
- 在上下文管理器外创建执行器。
- 客户端断开连接时,取消待处理任务而非等待关闭。
- 在
finally中调用executor.shutdown(wait=False, cancel_futures=True)。
- 原理:上下文管理器调用
shutdown(wait=True),如果RSS工作线程因网络I/O挂起,将阻塞事件循环。
实施步骤
- 更新流执行器使用(
backend/app/api/routes/stream.py):
- 实例化
executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)。
- 通过
loop.run_in_executor(executor, _process_source_with_debug, ...)分派工作。
- 断开连接时,
cancel()待处理任务。
- 在
finally中,executor.shutdown(wait=False, cancel_futures=True)。
- 保持RSS执行器不变(
rss_ingestion.py),因其在后台线程运行,但需确保请求超时保持合理(当前每个RSS requests.get为60秒)。
- 重新测试:
- 重启uvicorn;
curl -m 5 http://localhost:8000/health应正常响应。
- 启动流请求并中止客户端;服务器必须保持响应。
- 重新运行
py-spy dump验证主线程中没有ThreadPoolExecutor.shutdown(wait=True)帧。
验证清单
- [ ]
curl -m 5 http://localhost:8000/返回响应(无挂起)。
- [ ]
curl -m 5 http://localhost:8000/health成功。
- [ ] 中止
/news/stream不会冻结后续请求。
- [ ]
py-spy dump显示事件循环未被ThreadPoolExecutor.shutdown阻塞。
- [ ] 后端处理流时,前端不再因等待根路径/健康检查而卡住。
备注与未来加固
- 考虑添加请求超时中间件以快速处理慢速处理器。
- 为RSS源添加每个源的网络超时和更短重试,以减少长生命周期线程。
- 如果使用多工作进程uvicorn,诊断挂起时在每个工作进程pid上运行
py-spy。