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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 09:35:22