如何配置systemd服务以正确停止Python程序并删除锁文件?
解决systemd停止服务时锁文件未删除的问题
咱们来拆解下你遇到的问题:手动按Ctrl+C退出程序时锁文件能正常删除,但用systemd的停止命令(比如systemctl stop your-service)就不行,核心原因是两种场景发送的终止信号不一样,你的Python代码只处理了Ctrl+C对应的信号。
为什么会出现这个差异?
- 当你按
Ctrl+C时,系统给程序发送的是SIGINT信号,Python会把它转换成KeyboardInterrupt异常,你的try-except块捕获到这个异常后,会执行close_program()和锁文件删除逻辑。 - 而systemd默认停止服务时,给主进程发送的是
SIGTERM信号,这个信号不会触发KeyboardInterrupt异常,所以你的程序没进入异常处理分支,锁文件自然没被删除。
下面给你几个可行的解决方案,按需选择:
方案1:修改Python代码,处理多种终止信号
通过signal模块注册信号处理函数,让程序在收到SIGINT(Ctrl+C)和SIGTERM(systemd默认停止信号)时都执行清理逻辑。修改后的代码示例:
import signal import os import pathlib from datetime import datetime import logging logger = logging.getLogger(__name__) def clean_up_and_exit(signum, frame): """处理终止信号,执行清理操作并退出""" logger.info("Received stop signal, starting cleanup...") close_program() # 关闭你的其他线程 lock_path = pathlib.Path("program.lock") if lock_path.exists(): os.remove(lock_path) logger.info("Lock file removed successfully.") exit(0) # 注册信号处理:同时监听SIGINT和SIGTERM signal.signal(signal.SIGINT, clean_up_and_exit) signal.signal(signal.SIGTERM, clean_up_and_exit) if __name__ == '__main__': lock_path = pathlib.Path("program.lock") if not lock_path.exists(): lock_acquired_on = datetime.now() with open(lock_path, 'w') as lock: lock.write(f'Lock acquired on {lock_acquired_on}') logger.info('Added lock file to avoid running the program twice.') try: while True: # 你的业务逻辑代码 pass except Exception as e: logger.error(f"Unexpected error: {e}") clean_up_and_exit(None, None) else: with open(lock_path, 'r') as lock: lock_acquisition_time = str(lock.readlines()[0]) logger.info('Programme Maquette Status is already running.') logger.info(lock_acquisition_time)
这个方案最健壮,能覆盖所有常见的程序终止场景。
方案2:修改systemd服务配置,发送SIGINT信号
如果你不想改代码,可以让systemd停止服务时发送SIGINT信号(和Ctrl+C一致),这样就能触发你现有的KeyboardInterrupt异常处理。修改你的systemd配置文件,在[Service]段添加一行:
[Service] WorkingDirectory=/home/pi/Documents/ProductionMaquette Type=simple ExecStart=/usr/local/bin/python3.8 /home/pi/Documents/ProductionMaquette/Lo_main.py StandardInput=tty-force # 添加这行,让systemd发送SIGINT而不是默认的SIGTERM KillSignal=SIGINT
修改后重新加载systemd配置并重启服务:
sudo systemctl daemon-reload sudo systemctl restart your-service-name
这个方案改动最小,适合快速解决问题。
额外建议:利用systemd内置机制避免重复启动
其实systemd本身就有防止服务重复运行的能力,不需要自己手动实现锁文件:
- systemd会跟踪服务的主进程,默认不会允许同一个服务同时启动多个实例;
- 如果需要更严格的控制,可以在
[Service]段添加PIDFile=/path/to/your/pid/file,让systemd通过PID文件管理进程,但对于Type=simple的服务,这个通常不是必须的。
当然,你自己实现的锁文件可以作为额外的保障,两者不冲突。
内容的提问来源于stack exchange,提问作者Welyweloo
相关产品推荐
相关产品推荐

