flask 不处理系统信号,gunicorn worker 进程不会继承主进程的 signal 处理器,必须在每个 worker 初始化时显式注册;正确做法是在 when_ready 钩子中用 signal.signal() 注册,并配合标志位而非直接调用 cleanup。

Flask 的 signal 模块根本不在 Flask 运行时起作用
Flask 本身不使用 Python 标准库的 signal 模块做内部调度或生命周期管理。你写的 signal.signal(signal.SIGTERM, handler) 确实会注册成功,但仅对**当前进程的主解释器线程**有效;而生产部署(如 Gunicorn)中,每个 worker 是独立子进程,且 Flask 应用逻辑运行在 worker 内部——它既不监听信号,也不转发信号给你的 handler。换句话说:信号能发到进程,但 Flask 不“接招”,你的 handler 可能压根没机会执行。
Gunicorn worker 进程不继承主进程的 signal 处理器
当你运行 gunicorn -w 4 app:app,Gunicorn 主进程 fork 出 4 个 worker 子进程。fork 后,子进程会复制父进程的 signal 处理器注册状态,但 Python 的 signal 模块在子进程中默认重置为 SIG_DFL(系统默认行为),除非你显式在每个 worker 初始化时重新注册。
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
- worker 启动后,
os.getpid()和signal.getsignal(signal.SIGTERM)会显示为signal.Handlers.SIG_DFL - 即使你在
app.py顶层写了signal.signal(...),它只在主模块导入时执行一次,而 Gunicorn 的 worker 是通过import app+exec方式加载,不保证该代码块被执行 - 更隐蔽的问题:Gunicorn 自身会捕获 SIGTERM 并优雅关闭 worker,你的 handler 若未在 worker 内注册,会被完全绕过
正确做法:在每个 worker 中注册 signal handler,且必须早于应用启动
不能依赖 Flask 的初始化时机,必须确保 signal 注册发生在 worker 的 Python 解释器刚启动、任何业务代码执行前。最可靠的位置是 Gunicorn 的 on_starting 或 when_ready 钩子,或者直接在 WSGI 入口文件中用 if __name__ == '__main__': 包裹(但此方式不适用于 Gunicorn 场景)。
- 推荐方案:在
gunicorn.conf.py中定义def when_ready(server):,并在其中调用signal.signal(...) - 必须用
signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGTERM])配合多线程 worker(如果启用--threads)防止信号被错误线程接收 - handler 内避免阻塞操作(如数据库写入、HTTP 请求),应只设标志位,由主循环轮询退出
- 记得同时处理
SIGINT(调试时 Ctrl+C)和SIGTERM(容器/系统终止),两者行为需一致
Flask 扩展(如 Flask-SQLAlchemy)的 cleanup 不响应 signal
很多开发者误以为调用 db.session.remove() 或 cache.clear() 放在 signal handler 里就能释放资源——但这是错的。这些对象绑定在当前 worker 的 Flask app context 或 SQLAlchemy engine 上,而 signal handler 运行在无 context 环境下,current_app 和 db 可能未初始化或不可访问。
- 正确路径:在 handler 中只设置全局 flag(如
should_exit = True),然后让 Flask 路由或后台任务定期检查该 flag 并触发 cleanup - SQLAlchemy session 必须在请求上下文或明确的 app context 中调用
remove(),否则会报RuntimeError: Working outside of application context - Redis 连接、文件句柄等底层资源,应在
atexit.register()中清理,但它不响应 SIGTERM(只响应正常退出),所以仍需 signal + flag 双保险
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










