上个月,我的 AI 伙伴 Hermes 出了个诡异的毛病。

systemctl --user restart hermes-gateway 之后,旧进程退不出来,新进程启动不了。有时候卡 5 分钟,有时候直接挂了。日志里全是 Failed with result 'timeout'

前后折腾了三周,从现象到根因,最后三处改动全部解决。记一下过程。

症状

钰哥(我)长期反馈:Hermes Gateway 通过 systemd 重启时有两个问题:

  1. 「停不了」 — 旧进程不会在合理时间内退出,systemd 被迫发 SIGKILL
  2. 「起不来」 — 新进程有时无法正常启动,restart counter 飙升

调查

journalctl 是第一手证据:

systemd 22:26:03 hermes-gateway.service: Killing process 511707 (python) with signal SIGKILL.
systemd 22:26:03 hermes-gateway.service: Failed with result 'timeout'.

每次都是精确的 60 秒超时。

去查代码,发现 gateway 的 shutdown 流程在等 Agent 跑完当前回合。Agent 的 max_iterations=90——一次聊天可以跑 90 轮 tool call,复杂任务拖个一两分钟是常事。而 systemd 的 TimeoutStopSec=60 和 gateway 内部的 drain_timeout=60 完全对齐,1 毫秒的余量都没有。

这还没完。KillMode=mixed 意味着 SIGKILL 杀的是整个 control group——主进程、子进程、终端里的 bash、sleep、tirith……连锅端。子进程被强制杀死,socket 来不及正常关闭,新进程启动时端口冲突——「起不来」的根因。

修复

三处改动,互不依赖:

  1. 预切断 Agent 循环 — shutdown 时不等 drain 超时就 interrupt Agent,让它在当前 API call 返回后立即退出
  2. systemd 配置 — KillMode=process(只杀主进程)+ TimeoutStopSec=90(留足余量)
  3. 端口等待 — kill 旧进程后等 socket 释放,最多 15 秒,超时跳过

复盘

这个问题的本质是两个独立问题完美叠加

  • drain 的逻辑设计假设 Agent 几秒就能结束,但复杂任务远超这个假设
  • systemd 的配置没有为 gateway 的特殊性留出余量
  • 导致结果:一个本来只会「慢一点」的问题,变成「直接挂了」的问题

最大的教训还是那句老话:日志告诉你哪里出错,不告诉你为什么出错。