systemd timer运行Python脚本时subprocess.run调用mail致DMA发送卡住的问题
出现这个差异的核心原因在于systemd运行环境与终端命令行环境的资源管理、进程控制逻辑不同,具体分为几点:
Systemd控制组的进程清理机制
systemd默认使用KillMode=control-group策略,当Python脚本(主进程)退出后,会终止其控制组内的所有子进程——包括mail调用的sendmail/DMA进程。这就导致DMA还在处理邮件时被强制终止,邮件留在队列。而终端命令行中,进程属于用户会话的控制组,主进程退出后子进程不会被立即清理,能完成发送流程。标准流未正确处理
DMA的sendmail进程可能会尝试向stdout/stderr输出日志或状态信息,但当subprocess.run()完成后,Python脚本会关闭这些管道。如果DMA进程还在尝试写入已关闭的流,就会因I/O阻塞挂起,无法完成邮件发送。环境与权限差异
systemd timer运行的服务环境变量(如PATH、DNS配置)比终端精简,且可能以不同用户身份运行,导致DMA初始化、网络连接出现延迟;或者DMA队列目录(通常是/var/spool/dma)的权限限制,让systemd运行的进程无法正常读写队列文件,sleep给了足够时间完成权限验证或资源初始化。
方法1:修改Systemd服务配置,避免清理子进程
编辑你的systemd服务文件(如myscript.service),在[Service]段添加:
[Service] # 仅终止主进程,保留子进程继续运行 KillMode=process # 可选:指定工作目录,避免权限问题 WorkingDirectory=/path/to/your/script/directory # 可选:继承完整环境变量(如果需要依赖终端环境的配置) Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
修改后重新加载systemd配置:
systemctl daemon-reload
方法2:优化subprocess调用,隔离子进程
修改Python代码中的subprocess.run()参数,让mail进程在新会话中运行,同时正确处理标准流:
import subprocess body = "Something is wrong" subprocess.run( ["mail", "-s", "[myscript@myserver] - ALERT", "mymail@domain.com"], input=body.encode(), # 重定向输出到/dev/null,避免DMA因管道关闭阻塞 stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, # 让mail进程脱离原控制组,避免被systemd清理 start_new_session=True, # 可选:检查命令执行状态,方便排查错误 check=True )
方法3:配置DMA为同步发送
编辑DMA的配置文件(通常是/etc/dma/dma.conf),设置同步发送模式,让mail命令等待DMA完成发送后再退出:
# 强制DMA同步发送,mail命令会等待发送完成再返回 SENDMODE=sync
修改后重启DMA服务:
systemctl restart dma.service
- 查看DMA实时日志,定位具体报错:
journalctl -u dma.service -f
- 对比终端与systemd的环境变量差异:在脚本中添加
print(os.environ),分别在终端和timer运行时输出,找出缺失的关键变量。 - 检查DMA队列目录权限:确保systemd服务运行的用户对
/var/spool/dma拥有读写权限。
内容的提问来源于stack exchange,提问作者Drobot Viktor

