systemd发送SIGINT至Python守护进程时subprocess异常问题排查
systemd的systemctl kill默认采用control-group模式,会把信号发送给服务所属控制组(cgroup)内的所有进程,而非仅主进程。你通过os.setsid()创建的新会话组,只能隔离终端会话发送的信号(比如直接运行时的Ctrl+C),但挡不住systemd基于cgroup的信号广播——子进程仍属于服务的cgroup,所以会被一并发送SIGINT,导致崩溃。
按侵入性从低到高排序:
1. 修改systemd服务配置(推荐,最少代码改动)
在myapp.service文件里添加这些配置,让systemd只给主进程发信号:
[Service] # 仅向主进程发送终止信号,不波及子进程 KillMode=main # 可选:让systemctl stop默认发SIGINT而非SIGTERM,匹配你的信号处理逻辑 KillSignal=SIGINT
之后执行sudo systemctl stop myapp或sudo systemctl kill --signal=SIGINT myapp,只有Python主进程会收到SIGINT,子进程不会被systemd直接发信号,你的原有逻辑就能正常工作。
2. 增强子进程的信号忽略逻辑
在subprocess.Popen的preexec_fn里,除了创建新会话,直接让子进程忽略SIGINT:
import os import signal import subprocess def preexec_func(): os.setsid() # 子进程收到SIGINT直接忽略 signal.signal(signal.SIGINT, signal.SIGIGN) # 调用Popen时使用这个自定义函数 subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=preexec_func)
这样就算systemd给子进程发SIGINT,子进程也会无视,只有主进程处理信号并控制关闭流程。
3. 自定义ExecStop命令
如果不想修改默认KillMode,可以在service文件里指定ExecStop,让systemd只给主进程发信号:
[Service] # 仅向主进程发送SIGINT ExecStop=/bin/kill -SIGINT $MAINPID
执行systemctl stop myapp时,systemd会执行这条命令,只给主进程发信号,不会影响子进程。
改完配置后,重启服务生效:
sudo systemctl daemon-reload sudo systemctl restart myapp
再执行sudo systemctl kill --signal=SIGINT myapp,查看日志确认只有主进程收到信号,子进程能正常运行到主进程控制关闭。
内容的提问来源于stack exchange,提问作者Paranoid Altoid

