Flask应用收到SIGTERM后任务被杀是因为默认不处理信号且无优雅关闭机制;需结合gunicorn的graceful-timeout配置、请求生命周期标记、线程同步及后台任务显式收尾。

Flask应用收到SIGTERM后为什么任务还会被杀掉?
默认情况下,Flask开发服务器(app.run())不处理系统信号,进程收到SIGTERM时直接退出,正在执行的请求或后台任务(比如数据库写入、文件上传、异步通知)会被强制中断。生产环境用gunicorn或uWSGI也一样——worker进程被kill前不会等待当前请求完成。
用atexit和signal.signal注册清理函数不够用
atexit.register()只在Python解释器正常退出时触发,而signal.signal(signal.SIGTERM, handler)能捕获信号,但仅靠它无法阻止worker被立即终止。关键在于:你得让主循环/服务器知道“现在别杀我,等这个请求做完”。
- Web服务器(如
gunicorn)有自己的worker生命周期管理,需配合其预退出钩子 -
flask本身无内置“优雅关闭”机制,必须依赖部署层协同 - 若用了
threading或concurrent.futures启动后台任务,需手动维护threading.Event或ThreadPoolExecutor.shutdown(wait=True)
gunicorn配置中必须设置timeout和graceful-timeout
这是最常被忽略的硬性配置点。仅改代码没用,gunicorn默认--timeout 30会强制kill卡住的worker,而--graceful-timeout才是留给优雅关闭的窗口。
-
--timeout 120:防止长请求被误杀(比如大文件上传) -
--graceful-timeout 30:收到SIGTERM后,最多等30秒让worker自行退出 -
--workers 4 --worker-class sync:避免gevent/eventlet掩盖阻塞问题 - 启动命令示例:
gunicorn --bind :8000 --timeout 120 --graceful-timeout 30 myapp:app
在Flask中监听请求生命周期并标记“忙碌状态”
光靠外部配置还不够,你需要让应用自己感知“当前是否有活跃请求”,并在收到信号时拒绝新请求、等待旧请求结束。推荐用一个全局threading.Event配合before_request/teardown_request:
import threading
import time
from flask import Flask, request
<p>app = Flask(<strong>name</strong>)
shutdown_event = threading.Event()
is_shutting_down = False</p><p>@app.before_request
def block_if_shutting_down():
if is_shutting_down:</p><h1>拒绝新请求(返回503)</h1><pre class="brush:php;toolbar:false;"> from flask import abort
abort(503)@app.teardown_request def mark_request_done(exception):
请求结束时检查是否要触发关机
if shutdown_event.is_set() and not app.request_started:
# 这里可加逻辑:等待所有活跃请求计数归零
passdef shutdown_server(): global is_shutting_down is_shutting_down = True shutdown_event.set()
等待最多30秒,或所有请求自然结束
shutdown_event.wait(timeout=30)
然后在signal handler里调用shutdown_server(),并确保gunicorn的--graceful-timeout ≥ 这个等待时间。
真正难的是后台非HTTP任务——比如用APScheduler或celery触发的定时作业。它们不走Flask请求周期,必须单独注册shutdown回调,并用executor.shutdown(wait=True)或celery.control.cancel_consumer()显式收尾。这点很容易漏掉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











