如何让作为systemd服务的Python脚本在systemctl stop时正常退出?
解决systemd停止Python服务时未触发异常处理的问题
我完全懂你遇到的困扰——手动跑脚本按Ctrl+C能正常触发KeyboardInterrupt、执行退出逻辑,但用systemd管理时,systemctl stop却没触发你的except块,连"exiting"日志都看不到。这其实是因为systemd默认发送的信号,和你代码里处理的信号根本不匹配。
问题根源
当你执行systemctl stop停止服务时,systemd默认发送的是SIGTERM终止信号,而你的代码里只处理了三类情况:
KeyboardInterrupt:对应Ctrl+C发送的SIGINT信号SystemExit:Python内部的主动退出异常Exception:通用异常,但SIGTERM不会直接抛出这类异常
所以SIGTERM信号到来时,你的脚本没有对应的处理逻辑,直接被强制终止,自然不会执行except块里的代码。
两种实用解决方案
方案1:修改systemd服务配置,让它发送SIGINT
最简单的办法是让systemd在停止服务时,发送和Ctrl+C一样的SIGINT信号,这样你的现有代码就能直接捕获KeyboardInterrupt。
编辑你的systemd服务文件(比如/etc/systemd/system/myscript.service),在[Service]段添加:
KillSignal=SIGINT
然后重新加载配置并重启服务:
sudo systemctl daemon-reload sudo systemctl restart myscript.service
下次执行systemctl stop时,systemd就会发送SIGINT,你的except (KeyboardInterrupt, ...)块会正常触发,"exiting"日志也会输出到syslog里。
方案2:在Python代码中直接捕获SIGTERM信号
如果你不想修改systemd配置,也可以在代码里直接处理SIGTERM信号,把它接入你的退出逻辑。用signal模块注册信号处理函数即可,示例代码如下:
import signal import time import threading # 定义全局退出标志,让子线程也能感知到退出信号 exit_flag = False def handle_sigterm(signum, frame): """处理SIGTERM信号,设置退出标志""" global exit_flag exit_flag = True # 也可以直接抛出KeyboardInterrupt,让现有except块直接处理 # raise KeyboardInterrupt("Received SIGTERM from systemd") # 注册SIGTERM信号的处理函数 signal.signal(signal.SIGTERM, handle_sigterm) def worker_thread(thread_name): """子线程逻辑,需定期检查退出标志""" while not exit_flag: print(f"{thread_name} is running...") time.sleep(1) print(f"{thread_name} is exiting...") try: thread1 = threading.Thread(target=worker_thread, args=("Thread 1",)) thread2 = threading.Thread(target=worker_thread, args=("Thread 2",)) thread1.start() thread2.start() # 主线程循环等待退出标志 while not exit_flag: time.sleep(1) except (KeyboardInterrupt, SystemExit): # 处理Ctrl+C或其他主动退出场景 exit_flag = True print("exiting") finally: # 等待所有子线程正常结束,避免僵尸线程 thread1.join() thread2.join()
这个方案的核心逻辑:
- 用
signal.signal()注册SIGTERM的处理函数,收到信号时设置全局退出标志 - 子线程的循环逻辑要定期检查
exit_flag,确保能及时响应退出信号 finally块里调用join()等待子线程结束,保证程序完整、优雅地退出
日志相关注意点
如果还是看不到"exiting"日志,检查你的systemd服务文件配置:
- 默认情况下systemd会把stdout/stderr收集到journald,你可以用
journalctl -u myscript.service查看完整日志 - 如果要直接输出到syslog,可在
[Service]段添加StandardOutput=syslog和StandardError=syslog
内容的提问来源于stack exchange,提问作者Nazar
相关产品推荐
相关产品推荐

