daemon fork 后 sd_watchdog_enabled 返回 0
fork子进程可能继承父进程的WATCHDOG_PID。区分helper返回值与NotifyAccess权限,明确主进程交接和唯一心跳负责者。
服务配置了 WatchdogSec=,程序也能看到 WATCHDOG_USEC,但 fork 后的工作进程调用 sd_watchdog_enabled() 却返回 0。这种情况下先核对 WATCHDOG_PID 与当前进程 PID:子进程可能继承了属于父进程的看门狗环境。
sd_watchdog_enabled() 不只检查超时时间。若环境中有 WATCHDOG_PID,它还要求这个 PID 与调用进程一致。继承到子进程并不会自动改写该值。这个判断可以阻止无关的辅助进程把自己当成看门狗负责者。
先分清返回值和通知权限
检查调用位置:发生在 fork 前还是后,是否已经切换到最终工作进程,以及是否重复调用了会消费环境的初始化代码。
函数返回大于 0 才表示看门狗启用,此时输出参数中的超时时间才可用于安排心跳。返回 0 表示当前进程未启用;返回负值应按错误处理,不能把三者都写成“超时时间为零”。如果使用了非零的 unset_environment 参数,函数会移除相关环境变量,后续再次调用可能返回 0,不能据此判断 systemd 没有设置它们。
NotifyAccess= 是另一层规则,决定哪些进程发来的服务通知能够被接收。把它改为 all 不会修正 WATCHDOG_PID,也不会让 helper 忽略 PID 检查。服务主进程、环境中的 PID、实际发送通知的进程,需要分别核对。
程序通过 MAINPID= 通知更新主进程后,systemd 也不会进入已经运行的子进程,修改它继承的环境。
能以前台模式运行时,减少交接环节
如果程序支持前台运行和 systemd 通知,可以让受 systemd 启动的进程直接承担初始化、就绪通知和看门狗心跳,避免多次 fork 带来的负责者交接。配置必须符合程序实际支持的启动模式,不能只把 unit 改为 Type=notify 就认为它会发送 READY=1。
前台程序与 Type=forking 不匹配导致启动等待的问题,可另看 systemd 等待前台程序完成 fork。这里即使启动已经成功,也仍可能因为看门狗环境属于另一个 PID 而不发送心跳。
必须 fork 时,明确唯一的负责进程
保留 daemon 模式时,需要在程序的启动代码中设计交接。父进程先确认自己获得了有效的看门狗配置,并保存经过校验的超时时间;完成 fork 后,由最终工作进程接管。若最终进程继续使用 sd_watchdog_enabled(),应在调用前让看门狗环境指向它自己的 PID,并保留正确的 WATCHDOG_USEC。环境更新失败、PID 不匹配或 helper 返回负值,都不能继续按启用状态运行。
不能给所有子进程一律删除 WATCHDOG_PID,再让每个进程都发送 WATCHDOG=1。辅助进程持续发心跳,可能掩盖真正的工作进程已经卡死。交接前后也不应有两个长期并存的心跳负责者。
心跳周期通常取超时时间的一半,并应反映实际服务仍能工作。单独创建一个永远正常的定时线程,不能证明处理请求的主循环没有停住。程序采用独立线程时,也需要检查它是否获得了工作循环的存活证据。
修复后核对实际主进程与心跳发送者一致,helper 在负责进程中返回启用状态,通知能够被接收。还需要在可控制的验证环境中确认工作停滞后会触发看门狗处理;只看到服务保持 active,不能证明看门狗有效。


