如何解决uvicorn的set_timeout_graceful_shutdown方法导致的进程阻塞问题?

1. 问题背景

在使用Python的uvicorn库部署ASGI应用时,set_timeout_graceful_shutdown方法常用于实现优雅停机(Graceful Shutdown)。然而,开发者常遇到一个棘手问题:进程阻塞,即服务无法在预期时间内停止,导致资源无法释放或后续部署失败。

2. 问题现象

  • 调用set_timeout_graceful_shutdown后,服务未在超时时间内终止。
  • 日志中显示“Waiting for connections to close”但长期无进展。
  • 手动终止进程时触发SIGKILL信号,可能丢失部分请求数据。

3. 根本原因分析

该问题通常由以下因素导致:

  1. 长连接未关闭:WebSocket或HTTP长轮询连接未主动断开。
  2. 任务未完成:后台异步任务(如Celery作业)未处理完毕。
  3. 资源竞争:数据库连接池或文件锁未被释放。
  4. 信号处理冲突:与其他信号处理器(如SIGTERM)发生竞争。

4. 解决方案

4.1 强制终止长连接

# 在ASGI应用中主动关闭WebSocket连接  
async def websocket_disconnect_handler():  
    for connection in active_websockets:  
        await connection.close(code=1001)  

4.2 调整超时参数

通过uvicorn.Config增加graceful_timeouttimeout_keep_alive

config = uvicorn.Config(  
    app,  
    graceful_timeout=15,  # 默认10秒  
    timeout_keep_alive=5  # 防止Keep-Alive阻塞  
)  

4.3 使用进程监控工具

结合SupervisorGunicorn实现二次保护:

# Supervisor配置示例  
[program:myapp]  
stopwaitsecs=30  
killasgroup=true  

5. 高级调试技巧

工具用途
lsof -i :8000检查未关闭的套接字
strace -p PID跟踪进程系统调用
asyncpg连接池日志分析数据库连接泄漏

6. 最佳实践

建议在Kubernetes环境中结合preStop钩子readiness探针

# Kubernetes部署片段  
lifecycle:  
  preStop:  
    exec:  
      command: ["sh", "-c", "kill -SIGTERM 1"]