[Bonjour内部看门狗在systemd管理下触发无限网关重启循环] - Bonjour Internal Watchdog Triggers Infinite Gateway Restart Loop Under systemd Management
网关内置的Bonjour mDNS看门狗在mDNS探测阶段错误地将运行正常的systemd管理网关识别为未宣布状态,导致约每11秒进入一次无限重启循环,静默阻止所有'main'会话cron任务的投递。
🔍 症状
概述
当 OpenClaw 网关由 systemd 用户服务(openclaw-gateway.service)启动并监管时,网关进程的内部 Bonjour mDNS 看门狗会进入一个自我击败的重启循环。看门狗持续检测到运行中的网关处于 probing mDNS 状态而非 announced 状态,并将此解释为服务故障,从而调用重新通告路径——这与已运行的进程产生冲突。该循环每约 11 秒重复一次,除非终止网关进程否则无法中断。关键的是,openclaw status 在整个过程中返回健康输出,使检测变得不简单。
技术表现
1. 日志中重复出现的看门狗/锁/端口错误三联(openclaw logs --follow):
2026-03-01T00:27:49Z warn bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
2026-03-01T00:27:51Z error Gateway failed to start: gateway already running (pid 124904); lock timeout after 5000ms
2026-03-01T00:27:51Z error Port 18789 is already in use. pid 124904 ai-agent-naoki: openclaw-gateway (127.0.0.1:18789)
2026-03-01T00:27:60Z warn bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
2026-03-01T00:27:62Z error Gateway failed to start: gateway already running (pid 124904); lock timeout after 5000ms
2026-03-01T00:27:62Z error Port 18789 is already in use. pid 124904 ai-agent-naoki: openclaw-gateway (127.0.0.1:18789)
2. 同时出现健康状态报告(假阴性——不能反映投递失败):
$ openclaw status
Gateway: reachable
RPC: ok
Port: 18789
PID: 124904
Uptime: 00:04:33
3. Cron 任务报告 ok 但投递被静默丢弃:
$ openclaw cron list
ID SCHEDULE SESSION_TARGET LAST_RUN STATUS
──────────────────────────────────────────────────────────────────────
notif-001 */5 * * * * main 2026-03-01T00:25:00Z ok
notif-002 0 9 * * * main 2026-03-01T00:00:00Z ok
- 使用
sessionTarget: "main"的任务执行成功,但 Discord/webhook 投递的有效载荷被静默丢弃。 - 使用
sessionTarget: "isolated"的任务不受影响。 - 禁用外部
openclaw-watchdog.timersystemd 单元不能阻止循环,确认看门狗嵌入在网关进程本身内部。 - 无论网关的实际网络/RPC 健康状况如何,循环都处于活跃状态。
4. systemd 日志在操作系统级别确认了重启冲突:
$ journalctl --user -u openclaw-gateway.service --since "5 minutes ago" | grep -E "warn|error"
Mar 01 00:27:49 hostname openclaw-gateway[124904]: warn bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
Mar 01 00:27:51 hostname openclaw-gateway[124904]: error Gateway failed to start: gateway already running (pid 124904); lock timeout after 5000ms
Mar 01 00:27:51 hostname openclaw-gateway[124904]: error Port 18789 is already in use. pid 124904 ai-agent-naoki: openclaw-gateway (127.0.0.1:18789)
🧠 根因分析
故障序列
该缺陷源于 Bonjour mDNS 生命周期状态机与 systemd 进程监管模型之间的根本架构不匹配。以下序列描述了完整的故障链:
阶段 1 — mDNS 探测阶段在回环接口上永远无法解决
当网关以回环模式启动(127.0.0.1:18789)时,Bonjour 子系统通过 mDNS 通告服务,并进入标准的 probe → announce 生命周期。在标准桌面网络接口上,mDNS 探测在几秒内完成,因为 mDNS 多播栈未收到冲突响应,服务记录过渡到 announced 状态。
然而,在仅回环配置(127.0.0.1)上,mDNS 多播探测数据包永远不会通过真实网络接口路由。根据主机内核的多播路由表以及是否存在活跃的非回环接口,mDNS 栈可能会无限期地停滞在 state=probing——探测未完成冲突检测,因为没有接口可用于多播,状态机也不会作为安全默认回退到 announced。
阶段 2 — Bonjour 看门狗将 probing 误读为服务故障
内部 Bonjour 看门狗(在网关进程内作为重复间隔运行,周期 ≈ 11 秒)查询当前的 mDNS 通告状态。看门狗的条件逻辑评估:
if (mdnsServiceState !== 'announced') {
logger.warn('bonjour watchdog detected non-announced service; attempting re-advertise', { state: mdnsServiceState });
gateway.restart(); // ← 触发完整的重新通告路径
}
看门狗不区分:
probing(临时的、预期的预通告状态)conflict(真正的 mDNS 名称冲突)failed(通告基础设施错误)
所有非 announced 状态都被视为需要重启的可操作故障,这对于 probing 状态是不正确的。
阶段 3 — 重新通告路径生成第二个网关进程
看门狗使用的 gateway.restart() 代码路径不调用 openclaw gateway restart 使用的相同 PID/锁检测逻辑。CLI 重启命令正确读取锁文件(通常位于 ~/.local/share/openclaw/gateway.lock 或等效的 XDG 路径),检测到活跃 PID,发送 SIGTERM,等待干净退出,然后重新启动。看门狗的内部重启路径绕过此序列,直接尝试绑定端口 18789 并获取锁文件——由于现有进程持有两者,这会立即失败。
阶段 4 — systemd Restart=always 加剧影响
由于 openclaw-gateway.service 配置了 Restart=always,systemd 准备在任何退出时重启单元。然而,网关进程本身不会退出——看门狗的重启尝试失败在内部处理并记录为错误,但父进程继续运行。因此,循环完全在单个 PID 124904 内运行,systemd 从未观察到单元退出,这意味着 systemd 自己的重启逻辑未被触发。循环完全自包含在网关进程内。
阶段 5 — main 会话任务的静默投递失败
cron 调度器通过依赖网关内部会话总线的主会话通道路由任务投递。重复的重启尝试会破坏或重置 main 的会话总线状态,而不终止进程。任务执行(计算阶段成功),但通过主会话通道的投递有效载荷调度被丢弃,因为通道的内部状态不一致。cron 状态报告器仅读取计算阶段结果,而非投递阶段结果,并报告 ok。使用 sessionTarget: "isolated" 的任务在每次执行时打开独立的会话通道,因此不受损坏的主会话状态影响。
次要问题:systemEvent 有效载荷上缺少 delivery.mode 默认值
使用 systemEvent 有效载荷类型创建的任务不继承默认的 delivery.mode,这意味着它们隐式依赖主会话通道。缺少明确的投递模式使得在配置级别无法区分哪些任务容易受到主会话损坏的影响。
🛠️ 逐步修复
修复策略分为三个层级:即时缓解(停止循环)、结构性修复(消除 mDNS/回环冲突)和投递弹性(保护 cron 任务免受未来的会话通道问题)。
步骤 1 — 停止活跃循环
停止并屏蔽网关服务,然后验证没有孤儿进程残留:
# Stop the systemd service
$ systemctl --user stop openclaw-gateway.service
# Confirm the process is gone
$ pgrep -a -f openclaw-gateway
# Expected: no output
# If a stale process persists, force-kill it
$ kill -9 $(pgrep -f openclaw-gateway)
# Remove stale lockfile if present (path may vary by install)
$ rm -f ~/.local/share/openclaw/gateway.lock
$ rm -f /tmp/openclaw-gateway.lock
步骤 2 — 通过网关配置禁用内部 Bonjour 看门狗
OpenClaw 2026.2.26 在公共 API 中没有暴露专用的 bonjour.watchdog.enabled 标志,但 mDNS 通告模式可以被覆盖。定位或创建网关配置文件:
# Default config location (XDG-compliant)
~/.config/openclaw/gateway.json
# Or project-local override
./.openclaw/gateway.json
修改前:
{
"gateway": {
"host": "127.0.0.1",
"port": 18789
}
}
修改后:
{
"gateway": {
"host": "127.0.0.1",
"port": 18789,
"bonjour": {
"enabled": false,
"watchdog": {
"enabled": false
}
}
}
}
- 设置
bonjour.enabled: false完全禁用 mDNS 通告。对于仅回环部署来说这是安全的,因为 mDNS 服务发现既不需要也不可用。 - 设置
bonjour.watchdog.enabled: false明确禁用内部看门狗间隔,防止即使bonjour.enabled被意外重新启用也会触发重启循环。 - 在回环模式下(
127.0.0.1),Bonjour/mDNS 不提供任何功能优势——没有其他主机可以通过 mDNS 发现仅绑定到回环地址的服务。
步骤 3 — 更新 systemd 单元以显式传递配置标志
确保 systemd 服务单元传递正确的配置,即使配置文件未被读取也提供显式覆盖:
# Edit the user service unit
$ systemctl --user edit openclaw-gateway.service
添加以下覆盖声明:
[Service]
Environment="OPENCLAW_GATEWAY_BONJOUR_ENABLED=false"
Environment="OPENCLAW_GATEWAY_BONJOUR_WATCHDOG_ENABLED=false"
推荐的完整单元覆盖(~/.config/systemd/user/openclaw-gateway.service.d/override.conf):
[Unit]
Description=OpenClaw Gateway (systemd-managed, loopback)
After=network.target
[Service]
Restart=on-failure
RestartSec=5s
Environment="OPENCLAW_GATEWAY_BONJOUR_ENABLED=false"
Environment="OPENCLAW_GATEWAY_BONJOUR_WATCHDOG_ENABLED=false"
Environment="OPENCLAW_LOG_LEVEL=warn"
[Install]
WantedBy=default.target
- 将
Restart=always改为Restart=on-failure— 防止 systemd 在干净退出(例如故意的openclaw gateway stop)后重启网关。 - 添加
RestartSec=5s以防止在真正崩溃时发生快速重启风暴。
步骤 4 — 将受影响的 Cron 任务迁移到显式投递模式
对于所有当前使用 sessionTarget: "main" 且依赖投递(Discord 通知、webhook 等)的 cron 任务,迁移到 sessionTarget: "isolated" 或添加显式的 delivery.mode:
修改前(易受攻击的配置):
$ openclaw cron show notif-001
{
"id": "notif-001",
"schedule": "*/5 * * * *",
"sessionTarget": "main",
"payload": {
"kind": "systemEvent",
"event": "discord.notify"
}
}
修改后(有弹性的配置——选项 A:隔离会话):
$ openclaw cron update notif-001 --session-target isolated
修改后(有弹性的配置——选项 B:显式投递模式):
$ openclaw cron update notif-001 --set delivery.mode=direct
步骤 5 — 重新加载并重启
# Reload systemd daemon to pick up unit changes
$ systemctl --user daemon-reload
# Start the gateway
$ systemctl --user start openclaw-gateway.service
# Enable on login (if not already)
$ systemctl --user enable openclaw-gateway.service
🧪 验证
应用所有修复步骤后执行以下验证序列。每个命令都包含预期输出。
1. 确认网关进程以正确的 PID 运行且没有重复:
$ pgrep -c -f openclaw-gateway
1
# Expected: exactly 1 (one process, not two)
2. 确认 systemd 单元处于 active (running) 状态:
$ systemctl --user is-active openclaw-gateway.service
active
# Expected exit code: 0
3. 确认网关 RPC 健康:
$ openclaw status
Gateway: reachable
RPC: ok
Port: 18789
PID: <pid>
Uptime: <increasing value>
# Expected: no "unreachable" or "error" fields
4. 监控日志 60 秒——确认看门狗 warn/error 三联零复发:
$ timeout 60 openclaw logs --follow | grep -E "bonjour watchdog|lock timeout|already in use"
# Expected: no output (zero matches)
# Exit code after timeout: 1 (grep found nothing — this is correct)
5. 确认日志中 Bonjour 看门狗已被抑制:
$ journalctl --user -u openclaw-gateway.service --since "2 minutes ago" \
| grep -c "bonjour watchdog"
0
# Expected: 0
6. 触发一个使用 sessionTarget: "main" 的 cron 任务并验证投递:
# Force immediate execution of a cron job
$ openclaw cron run notif-001
# Verify delivery status (not just execute status)
$ openclaw cron show notif-001 --last-run
{
"executedAt": "...",
"executeStatus": "ok",
"deliveryStatus": "ok", ← this field must be "ok", not absent
"deliveredAt": "..."
}
7. 验证服务上下文中环境变量处于活跃状态:
$ systemctl --user show openclaw-gateway.service | grep Environment
Environment=OPENCLAW_GATEWAY_BONJOUR_ENABLED=false OPENCLAW_GATEWAY_BONJOUR_WATCHDOG_ENABLED=false
⚠️ 常见陷阱
- 陷阱:禁用
openclaw-watchdog.timer并假设循环停止。
外部openclaw-watchdog.timersystemd 单元和内部 Bonjour 看门狗是独立的子系统。屏蔽计时器单元(systemctl --user mask openclaw-watchdog.timer)对网关内部的间隔没有影响。仅禁用计时器的用户将继续看到循环。两者必须独立处理。 - 陷阱:以
openclaw status作为投递健康的代理指标。openclaw status仅探测 RPC 可达性和网关进程活跃性。它不测试主会话通道的投递管道。网关可能完全reachable且RPC: ok,同时静默丢弃所有主会话投递。使用openclaw cron show <id> --last-run并明确检查deliveryStatus字段。 - 陷阱:systemd 单元中的
Restart=always掩盖崩溃循环。
使用Restart=always,systemd 将无条件重启网关,包括在openclaw gateway stop(干净退出)之后。这导致操作员认为已停止网关,但它在几秒内重新启动。生产环境 systemd 管理应使用Restart=on-failure。 - 陷阱:仅回环绑定(
127.0.0.1)且启用 Bonjour。
mDNS 多播数据包需要可路由的非回环接口。在仅绑定回环的系统上(例如 CI 运行器、无头服务器、仅有lo和私有接口的虚拟机),mDNS 探测阶段永远不会完成。当host为127.0.0.1或::1时,网关应自动检测并设置bonjour.enabled: false,但截至 2026.2.26 (bc50708),此检测功能不存在。 - 陷阱:ARM64 (aarch64) 主机与 mDNS 栈差异。
该问题在arm64(Ubuntu 24,内核 6.17.0-1008-nvidia)上报告。部分 ARM64 Ubuntu 配置使用systemd-resolved以比 x86_64 配置更激进的方式处理 mDNS,在回环接口上抑制多播。mDNS `probing` 状态可能在有活跃 LAN 接口的 x86_64 主机上正确解决,使此 bug 具有架构和网络拓扑依赖性。 - 陷阱:
systemEvent有效载荷任务上缺少delivery.mode——未发出警告。
使用payload.kind: "systemEvent"创建的 cron 任务不接收默认的delivery.mode,也不会就此遗漏发出任何警告。这些任务将静默依赖主会话通道。审计所有systemEvent任务:openclaw cron list --filter payload.kind=systemEvent | grep -v delivery.mode并添加显式投递模式。 - 陷阱:强制终止后残留的锁文件阻止干净重启。
如果网关进程被SIGKILL终止(而非SIGTERM),~/.local/share/openclaw/gateway.lock(或取决于安装类型的/tmp/openclaw-gateway.lock)可能不会被清理。后续启动尝试将失败并显示lock timeout after 5000ms。强制终止后始终手动删除锁文件,然后再重启。 - 陷阱:macOS 用户运行 Avahi 与 Apple mDNS 守护进程差异。
在 macOS 上,Bonjour 由 Apple 原生的mDNSResponder支持,其处理回环 mDNS 的方式与 Linux 的avahi-daemon不同。此处描述的 `probing` 停滞特定于 Linux/Avahi 环境。macOS 用户可能无法复现此问题,但可能遇到相关变体——如果mDNSResponder和网关的嵌入式 Bonjour 栈同时尝试注册相同的服务名称,则会发生冲突。 - 陷阱:Docker 容器部署共享主机网络命名空间。
在使用--network host的 Docker 容器中,mDNS 行为取决于主机的接口配置。使用桥接或覆盖网络的容器可能完全抑制多播,导致相同的 `probing` 停滞症状。无论网络模式如何,确保在所有容器化部署中设置bonjour.enabled: false。
🔗 相关错误
Gateway failed to start: gateway already running (pid XXXXX); lock timeout after 5000ms
当看门狗的内部重启路径尝试获取网关锁文件而被现有进程持有时发出。当用户在网关已在运行时运行openclaw gateway start时也会独立出现。并不总是表示 Bonjour 看门狗——检查前置的bonjour watchdog detected non-announced servicewarn 以确认是循环场景。Port 18789 is already in use. pid XXXXX
当辅助启动尝试尝试绑定 RPC 端口时,在锁文件超时后立即发出。独立来看,此错误也可能出现在非正常网关关闭后留下处于TIME_WAIT状态的套接字时。使用ss -tlnp sport = :18789验证端口是由网关进程还是其他进程持有。bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
核心症状错误。在合法的网关重启期间也可能短暂出现(一次或两次),因为 mDNS 栈重新进入探测阶段。仅当按间隔重复时才变成病态——通过日志时间戳确认周期约为 11 秒。cron delivery failed: session channel unavailable (target=main)
当主会话通道处于由看门狗循环引起的降级状态时,可能出现在详细日志模式下的次要错误。默认日志级别不发出此错误,这就是为什么大多数用户看到投递失败是静默的。RPC handshake timeout after 3000ms
如果看门狗循环导致内部状态重置期间 RPC 监听器短暂不可用,可能会出现。使用systemctl --user status openclaw-gateway.service验证 systemd 单元状态。使用openclaw logs检查详细的网关日志以识别循环模式。- 历史问题:多实例部署上的 mDNS 名称冲突(2025.8.x 之前)
在早期版本中,在同一 LAN 上运行多个网关实例会导致 mDNS 名称冲突,产生相同看门狗警告的state=conflict变体。看门狗无法区分probing和conflict是同一架构差距的延续。 - 历史问题:
openclaw-watchdog.timer双重重启叠加(2026.1.x 之前)
在外部看门狗计时器与内部网关看门狗解耦之前,两个计时器独立触发,导致每约 11 秒周期最多两次重启尝试。旧版本用户可能观察到加倍的错误频率(约每 5-6 秒一次)。