host: phase2 daemon + health + auto-recovery (restore, recover, double-fork)

This commit is contained in:
host committed 2026-10-26 00:57:01 +08:00
1 parent e9735d1fad
commit 240eaacf50
4 files changed
+434 -1

No files matched your search

+44
View File
@@ -394,3 +394,47 @@ tests/ 标准库 unittest,验收命令就是 `python3 -m unittest discover -s
包括 topo 排序、merge 验测通过/失败回滚/重复加自动解/
pyc 不入库、评审评论、重试成功/预算拦截/通知落盘、
state 版本冲突、server kill 重拉。
## 第二阶段:守护进程 + 健康检查 + 自动恢复(2026-10-26 凌晨,用户选“先做第二阶段”)
- **先堵的洞**:旧 `_read_state_file` 会把 JSON 解析错误
吞掉、当成空 state 返回——state 损坏一次就等于全部任务
静默丢失。新加 `load_state_strict`(文件存在但解析失败
时返回错误,不当空 state),health/recover 都用严格版。
- **health(不跑模型)**:查 codex 二进制存在、配置可读、
state 可解析、备份存在、daemon pid 存活/过期、server
能 initialize + list、缺失 worktree。实测:完好时 ok=true、
server_threads=7、exit 0;故意写坏 state 后 ok=false、
state_valid=false、带解析错误信息、exit 1。
- **restore-state**:state 损坏或丢失时按 .bak、.bak.1、
.bak.2 顺序找第一个能解析的备份,先把坏文件改名成
.corrupt.<时间戳> 留底,再把备份拷回。实测:坏 state +
version=9 的 .bak,恢复后 version=9,health 回到 ok=true。
- **recover(只标记,不自动重跑模型)**:status=running
且最后回合超过 --older-than(默认 3600 秒)未更新的
任务标 interrupted 并记日志,用户随后可用 run 续跑
(thread 在 CODEX_HOME 里不丢)。不自动重跑是刻意的
安全边界:自动重跑会不经用户同意花中转站额度。
实测:2 小时前的 stuck -> interrupted,1 分钟前的
fresh 保持 running;单测覆盖只标旧任务。
- **daemon(双 fork)**:daemonize 用 fork -> setsid ->
再 fork,孙进程 reparent 到 PID 1、写 pidfile、stdout/
stderr 重定向到 host-daemon.log,循环每 interval(默认
300 秒)跑 recover + run-due,每轮都是独立子进程调
host.py CLI,一轮崩了不带走 daemon 本体。实测(interval
5 秒):daemon pid 10776、PPID=1(活过 launcher 会话),
daemon-status alive=true,日志每 5 秒一个 tick;
daemon-stop 后 pid 消失、status alive=false、pidfile
删除、日志有 host daemon stopped。
- **VM 边界(诚实说)**:daemon 本身活不过平台回收,
和微信桥 serve 同理——回收后要重拉,Muse cron 定时调
run-due/health 才是 VM 上更稳的定时。本轮没有自动装
Muse cron 或 crontab,因为 run-due 到期会跑模型花额度,
装定时这一步留给用户显式下令(见下一条待办)。
- **回归**:tests 从 15 个涨到 19 个,全部通过,新增
corrupt 检测 + 从 .bak 恢复、无备份时不覆盖、recover
只标旧任务、health 用假 server 全通过。
- **待办(不在本轮自动做)**:Muse cron 定时调
`host.py --config <config> health`(无模型成本)和 /
或 `run-due`(到期才跑模型),需用户定间隔和是否允许
自动跑模型后才装。