如何清理超时请求以防止FastAPI服务内存泄漏?
解决FastAPI客户端超时后服务端请求未取消导致的内存泄漏问题
问题根源
客户端设置请求超时后,本地requests.post会抛出异常并终止等待,但Uvicorn默认不会主动检测客户端连接是否断开,服务端的请求处理任务(尤其是耗时的model.inference)会继续运行,占用内存无法释放,最终导致容器内存泄漏。
解决方案
1. 在请求处理中主动检测客户端连接状态
FastAPI的Request对象提供is_disconnected()方法,可判断客户端是否已断开连接。你可以在耗时操作的关键节点插入检查,一旦发现客户端断开就终止任务。
修改端点代码:
from fastapi import Request, HTTPException @app.post("/extract", response_model=ExtractionResponse) def extract(request: Request, req_data: ExtractionRequest) -> ExtractionResponse: # 先检查客户端状态 if request.is_disconnected(): raise HTTPException(status_code=499, detail="客户端已断开连接") # 如果模型推理支持插入中断检查,传入回调函数 def check_connection(): if request.is_disconnected(): raise RuntimeError("客户端断开,终止推理") try: # 假设model.inference接受检查回调参数 result = app.state.model.inference(req_data.sample, check_callback=check_connection) except RuntimeError as e: raise HTTPException(status_code=499, detail=str(e)) return ExtractionResponse(sample=result)
如果模型推理是无法拆分的阻塞操作,用线程包装任务并定期轮询:
from fastapi import Request, HTTPException import threading @app.post("/extract", response_model=ExtractionResponse) def extract(request: Request, req_data: ExtractionRequest) -> ExtractionResponse: result = None inference_error = None def run_inference(): nonlocal result, inference_error try: result = app.state.model.inference(req_data.sample) except Exception as e: inference_error = e thread = threading.Thread(target=run_inference) thread.start() # 轮询检查客户端状态和线程完成情况 while thread.is_alive(): thread.join(timeout=0.5) if request.is_disconnected(): # 需要模型支持主动终止(比如设置中断标志) app.state.model.abort_current_inference() raise HTTPException(status_code=499, detail="客户端断开,终止任务") if inference_error: raise inference_error return ExtractionResponse(sample=result)
2. 使用异步端点结合asyncio任务取消
如果模型推理可包装为异步操作(或用线程池异步执行),改用异步端点能更优雅地实现任务取消:
from fastapi import Request, HTTPException import asyncio @app.post("/extract", response_model=ExtractionResponse) async def extract(request: Request, req_data: ExtractionRequest) -> ExtractionResponse: # 将同步推理包装为异步任务 async def async_inference(): loop = asyncio.get_event_loop() return await loop.run_in_executor(None, app.state.model.inference, req_data.sample) inference_task = asyncio.create_task(async_inference()) disconnect_task = asyncio.create_task(request.is_disconnected()) # 等待第一个完成的任务 done, _ = await asyncio.wait( [inference_task, disconnect_task], return_when=asyncio.FIRST_COMPLETED ) # 客户端断开则取消推理任务 if disconnect_task in done: inference_task.cancel() raise HTTPException(status_code=499, detail="客户端已断开连接") result = inference_task.result() return ExtractionResponse(sample=result)
3. 优化Uvicorn启动参数
添加连接超时参数,配合端点内的检查减少无效连接占用:
CMD ["uvicorn", "--host", "0.0.0.0", "--port", "8080", "--timeout-keep-alive", "5", "main:app"]
--timeout-keep-alive设为5秒(和客户端超时一致),避免Uvicorn长时间保留已断开的连接。
关键注意事项
- 如果模型推理是完全阻塞且不支持中断的,上述方案只能检测客户端断开,但无法强制终止任务,需修改模型代码加入中断检查逻辑(比如循环中检查全局标志)。
- 499状态码是Nginx定义的"客户端关闭连接"状态,FastAPI支持返回该状态码标识客户端主动断开场景。
内容的提问来源于stack exchange,提问作者Kroshtan
相关产品推荐
相关产品推荐

