FastAPI应用需通过Uvicorn的ASGI lifespan协议响应SIGTERM实现优雅停机:在@asynccontextmanager定义的lifespan中,yield后执行await engine.dispose()等异步清理,确保数据库连接池、后台任务等安全释放,避免资源泄漏。

FastAPI应用如何响应SIGTERM实现优雅停机
默认情况下,FastAPI(基于Starlette + Uvicorn)收到SIGTERM会直接终止进程,中间件、数据库连接、后台任务等来不及清理。必须主动捕获信号并触发关闭逻辑。
关键不是“注册信号处理器”,而是让Uvicorn的事件循环能配合你的清理流程——直接在signal.signal(signal.SIGTERM, ...)里调用sys.exit()会导致资源泄漏,因为Uvicorn主线程可能还在处理请求。
- 正确做法是:通过Uvicorn的
lifespan协议,在shutdown阶段释放资源;再配合uvicorn --forwarded-allow-ips等部署参数确保信号能透传到主进程 - 若用
systemd或K8s,务必配置StopSec=30(或更长),给shutdown留出时间 - 不要在信号处理器中调用
asyncio.get_event_loop().stop()——Uvicorn 0.20+已弃用该模式,会引发RuntimeError: Event loop stopped before Future completed
用Lifespan上下文管理器释放数据库连接池与后台任务
FastAPI本身不提供生命周期钩子,但Uvicorn支持ASGI lifespan协议,FastAPI可通过lifespan参数接入。
典型场景:释放SQLAlchemy连接池、取消asyncio.create_task()启动的监控协程、关闭Redis订阅连接。
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
<p>engine = create_async_engine("sqlite+aiosqlite:///app.db")</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8054" title="python-parallelization"><img
src="https://img.php.cn/upload/skill/000/000/081/179181013081932.jpg" alt="python-parallelization" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="overflowclass">python-parallelization</a>
<p class="overflowclass">将顺序 Python 代码转换为并行/并发实现,用于在需要并行化 Python 代码、提升性能时使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>@asynccontextmanager
async def lifespan(app: FastAPI):</p><h1>startup</h1><pre class="brush:php;toolbar:false;">app.state.engine = engine
yield
# shutdown —— 这里会被SIGTERM触发
await engine.dispose() # 关闭所有连接
if hasattr(app.state, "monitor_task"):
app.state.monitor_task.cancel()
try:
await app.state.monitor_task
except asyncio.CancelledError:
passapp = FastAPI(lifespan=lifespan)
-
lifespan必须是asynccontextmanager,不能是普通函数 -
await engine.dispose()比engine.dispose()更安全,后者是同步阻塞调用,可能卡住事件循环 - 若使用
httpx.AsyncClient或aiofiles等,同样应在yield后显式关闭
为什么Uvicorn的--reload模式下SIGTERM无效
开发时用uvicorn main:app --reload,实际运行的是watchfiles父进程监听文件变更,真正服务进程是子进程。向父进程发SIGTERM只会重启子进程,不会走lifespan.shutdown。
- 测试优雅停机必须用
uvicorn main:app(无--reload)或gunicorn --worker-class uvicorn.workers.UvicornWorker - K8s中若用
livenessProbe频繁探测,可能误触发重启,应避免在/health里做耗时检查(如DB连通性),否则shutdown阶段可能被新请求打断 - 某些容器运行时(如Docker Desktop旧版)会把
SIGTERM转为SIGKILL,需在Dockerfile里加STOPSIGNAL SIGTERM
日志与超时控制:确保shutdown真正完成
Uvicorn默认等待60秒才强制杀进程(--timeout-graceful-shutdown),但这个时间不可见,容易误判停机失败。
- 在
lifespan的shutdown块开头打日志:logging.info("Starting graceful shutdown..."),结尾打"Graceful shutdown completed",用于确认是否执行到末尾 - 若依赖外部服务(如消息队列ACK),建议设
asyncio.wait_for(..., timeout=25.0),避免单个慢操作拖垮整个停机流程 - Uvicorn 0.23+ 支持
--timeout-graceful-shutdown 30,但该值必须小于进程管理器(如systemd的TimeoutStopSec)设定,否则会被提前强杀
最易被忽略的一点:lifespan中的异常不会被Uvicorn捕获,也不会打印堆栈——它会静默跳过shutdown,导致连接池残留。务必在try/except中包裹清理逻辑,并记录错误。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










