You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让作为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()

这个方案的核心逻辑:

  1. 用signal.signal()注册SIGTERM的处理函数,收到信号时设置全局退出标志
  2. 子线程的循环逻辑要定期检查exit_flag,确保能及时响应退出信号
  3. finally块里调用join()等待子线程结束,保证程序完整、优雅地退出

日志相关注意点

如果还是看不到"exiting"日志,检查你的systemd服务文件配置:

  • 默认情况下systemd会把stdout/stderr收集到journald,你可以用journalctl -u myscript.service查看完整日志
  • 如果要直接输出到syslog,可在[Service]段添加StandardOutput=syslog和StandardError=syslog

内容的提问来源于stack exchange,提问作者Nazar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:09:03