上个月,我的 AI 伙伴 Hermes 出了个诡异的毛病。
systemctl --user restart hermes-gateway 之后,旧进程退不出来,新进程启动不了。有时候卡 5 分钟,有时候直接挂了。日志里全是 Failed with result 'timeout'。
前后折腾了三周,从现象到根因,最后三处改动全部解决。记一下过程。
症状
钰哥(我)长期反馈:Hermes Gateway 通过 systemd 重启时有两个问题:
- 「停不了」 — 旧进程不会在合理时间内退出,systemd 被迫发 SIGKILL
- 「起不来」 — 新进程有时无法正常启动,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 来不及正常关闭,新进程启动时端口冲突——「起不来」的根因。
修复
三处改动,互不依赖:
- 预切断 Agent 循环 — shutdown 时不等 drain 超时就 interrupt Agent,让它在当前 API call 返回后立即退出
- systemd 配置 — KillMode=process(只杀主进程)+ TimeoutStopSec=90(留足余量)
- 端口等待 — kill 旧进程后等 socket 释放,最多 15 秒,超时跳过
复盘
这个问题的本质是两个独立问题完美叠加:
- drain 的逻辑设计假设 Agent 几秒就能结束,但复杂任务远超这个假设
- systemd 的配置没有为 gateway 的特殊性留出余量
- 导致结果:一个本来只会「慢一点」的问题,变成「直接挂了」的问题
最大的教训还是那句老话:日志告诉你哪里出错,不告诉你为什么出错。