python进程收到sigterm不退出,主因是signal.signal被覆盖且handler未调用sys.exit(),或主线程阻塞在不可中断调用中,需注册正确handler、改用可中断等待、统一用threading.event通知退出,并显式清理子进程、线程、连接池等资源。

Python 进程收到 SIGTERM 后不退出?检查 signal.signal 是否被覆盖
很多服务在容器或 systemd 下启动后,发 kill -15 没反应,本质是 Python 默认对 SIGTERM 的处理被意外替换了,或者新注册的 handler 里没调用 sys.exit() 或没触发退出逻辑。
- Python 启动时默认把
SIGTERM设为SIG_DFL(系统默认行为),但一旦你显式调用signal.signal(signal.SIGTERM, handler),就接管了它——而 handler 里如果只做日志、不终止主循环或没调用os._exit()/sys.exit(),进程就会卡住 - 常见于用了
gevent或asyncio的服务:它们可能内部重装了 signal handler,且未透传给用户逻辑 - 验证方式:在 handler 里加
print("got SIGTERM"),再kill -15 $PID,看是否打印;不打印说明 handler 没注册上,打印了却不退出说明逻辑漏了退出动作
主线程阻塞在 time.sleep() 或 queue.get()?用可中断的等待替代
优雅退出的前提是主逻辑能及时响应退出信号。但 time.sleep(30) 或 queue.get() 这类调用会无视信号,直到超时或有数据才返回,导致退出延迟长达几十秒。
-
time.sleep()改成带循环的短间隔轮询:for _ in range(300): time.sleep(0.1); if exit_event.is_set(): break -
queue.get()改用queue.get(timeout=1),并在捕获queue.Empty后检查退出标志 - 使用
threading.Event(如exit_event = threading.Event())统一通知各线程退出,比轮询信号更可靠 - 注意:
event.wait(timeout)是安全的,会被信号中断并抛出InterruptedError,可直接捕获后退出
asyncio.run() 启动的服务怎么接 SIGTERM?别依赖 asyncio 的默认行为
asyncio.run() 本身不处理信号,它只是运行一个 event loop 并等其关闭。SIGTERM 到来时,loop 不会自动 stop,协程也不会被 cancel,除非你手动干预。
- 必须在
asyncio.run()外层注册 signal handler,并在 handler 中调用loop.stop()或loop.create_task(shutdown()) - 推荐模式:用
asyncio.get_running_loop()获取当前 loop,在 shutdown 协程中await清理资源(如关闭 aiohttp.ClientSession、取消 pending task),再调用loop.stop() - 避免在 shutdown 协程里用
time.sleep()或同步阻塞调用;改用await asyncio.sleep() - 若用
uvicorn或fastapi,它们已内置信号处理,但自定义后台任务仍需手动监听asyncio.CancelledError
子进程、线程、连接池没清理干净?退出前必须显式 close/wait
“退出”不是 kill -9,而是释放所有持有资源。没关掉的数据库连接、没 join 的线程、没 terminate 的子进程,都会让服务假死或留下孤儿进程。
立即学习“Python免费学习笔记(深入)”;
- 数据库连接池(如
SQLAlchemy):调用engine.dispose(),不是只session.close() - 线程:设好
daemon=False的线程必须join(timeout=5),超时后考虑thread.ident是否还存活 - 子进程:用
subprocess.Popen启动的,要proc.terminate()+proc.wait(timeout=3),失败则proc.kill() - 文件句柄、socket、临时文件:确保用
with或显式.close();临时目录可用atexit.register(shutil.rmtree, path)做兜底
最常被忽略的是异步资源的生命周期管理——比如一个 background task 在 loop 关闭后还在跑,它持有的连接或锁可能永远得不到释放。退出流程里没有「等待所有非守护协程结束」这一步,就得靠你自己追踪和 cancel。










