Skip to content

v2026.10.02.1

Choose a tag to compare

@westsource westsource released this 02 Oct 08:52
· 11 commits to main since this release

v2026.10.02.1 — 后台同步主备改为持久租约 · 备副本自动接管

纯客户端发布:CLIENT_VERSION 2026.09.21.3 → 2026.10.02.1(mcp/server.py、mcp/updater.py、mcp/auto-sync.py、server/client_update.py),各端经 /api/client/manifest 自动更新、Agent 重启后生效。无 schema 变更,无服务端 API / Web UI 改动。触发场景:同一台机器开多个 Agent 窗口(每个窗口各 spawn 一个 MCP 客户端),实测只有一份在跑后台同步,而角色由"启动抢锁"一次性决定——关掉拥有后台循环的那个窗口后,其余窗口的后台同步会静默停摆。

Fixed(后台同步的角色与并发)

  • 主/备角色改为持久租约,备副本会自我提升:原先角色只在启动时决一次(O_EXCL 锁每周期释放 ⇒ 后启动的副本总能赢下已释放的锁并自认主,副本越多越像多主;而抢锁输家则终身 standby——omp 这类不重拉 MCP 进程的宿主上等于永久停摆)。改为 hermes-sync-<agent>.primary.json 租约(属主 pid + 心跳 + 上次成功同步时间,原子写、每周期刷新):启动时属主仍活着就做备(同时启动仍由 O_EXCL 锁选出唯一主),备每周期复查租约并在属主失联后接管继续跑后台循环。「失联」= 心跳已过一个完整 HERMES_SYNC_INTERVAL 且 pid 探测说死,或心跳超过 HERMES_SYNC_LEASE_STALE_S(默认 INTERVAL+120,兼作 pid 复用兜底);心跳优先,避免误判造出第二个主,代价是失联后最多晚一个周期接管(与同步间隔同量级)。顺带消除"每开一个窗口就重复一次启动全量同步"。
  • Windows 进程判活不再失真:os.kill(pid, 0) 实测三种问题——对 pid 4 抛 SystemError(不是 OSError,原实现接不住,启动任务直接崩、角色卡在 starting);对部分活进程报 WinError 87(被当成死的 ⇒ 会窃取活写者的锁);对刚退出但父进程仍持有句柄的进程报"活"。改用 OpenProcess(SYNCHRONIZE|QUERY_LIMITED_INFORMATION) + 零超时 WaitForSingleObject,不可判定(access denied)按"活"处理;POSIX 分支不变。
  • OpenClaw 常驻同步进程真的持锁了:mcp/auto-sync.py 的文档一直写"与 MCP server 共用同一把锁,绝不并发",但循环体从未取锁(会与 MCP server 的后台周期并发写同一本地库)。现在每轮经 run_once() 取同一把跨进程锁,拿不到就跳过本轮、下一 tick 重试。

Added(省事与可观测)

  • 启动同步新鲜度:上次成功同步的时间记在租约里(同机多窗口共享),距上次成功不足半个同步间隔就跳过启动拉取(另一个窗口刚同步过);手动 sync_full / sync_pull / sync_push 成功后同样刷新。
  • sync_status 增加 instance 段:pid、role、background_sync、lock_holder_pid、lease/lease_owner_pid/lease_owner_alive、log_file、client_version —— "谁在同步、有没有重复"不必再猜。
  • 日志落盘:hermes-sync-<agent>.log(与锁同目录,单代轮转为 .log.1,HERMES_SYNC_LOG_MAX_BYTES 默认 512 KiB)。宿主通常丢弃 MCP stderr(omp 全平台不留存),此前客户端进程崩溃没有留下任何可查线索;stderr 同时改为以 UTF-8 字节写出,避免中文 locale 下 GBK 编码让对方(含 MCP SDK 自身的 stdio 客户端)reader 线程解码失败。
  • 新环境变量:HERMES_SYNC_LEASE_FILE、HERMES_SYNC_LEASE_STALE_S、HERMES_SYNC_STARTUP_DELAY_S、HERMES_SYNC_LOG_FILE、HERMES_SYNC_LOG_MAX_BYTES。

测试与验证

  • 客户端 208 项 OK(194 → 208:新增租约存活判定、备按周期延后与自我提升、启动选举与让位、启动新鲜度、sync_status 实例段、日志落盘与轮转、auto-sync 持锁;删除把旧行为钉死的 standby 测试)。
  • 真机冒烟 13/13(两个真实 mcp/server.py 进程 + 私有锁/租约/日志目录 + server 指向不可达端口,避免触碰本地库):A 选举为主 → B 判为备且不写库 → 杀掉 A 后一个周期内 B 自我提升 → 新副本立刻让位给活属主,sync_status 全程如实回报角色与属主。
  • 236 部署:4 个文件对上一版提交 b8a55ae 逐文件预检通过、上传后回读 sha256 一致、py_compile 通过;重启后 active、journal 无 error、回环 /health=200。线上核对 9/9 PASS:/api/client/manifest?agent=omp&v=2026.09.21.3 返回 2026.10.02.1 + update_available=true、包内 manifest.json 版本一致且 mcp/server.py 的 sha256 与清单相符、包内含租约代码且不再含旧的 "standby 终身" 文案、含新存活探测、mcp/updater.py 为新版本号、mcp/auto-sync.py 持锁。

生产状态:236 已部署(代码提交 b07eb4d,记录提交 86a5ce5,tag v2026.10.02.1)。已装客户端会在下一次更新检查(每小时一次,或 Agent 重启后 60 秒)自动拉取新版本,重启 Agent 后新逻辑生效(正在运行的 MCP server 进程仍持旧代码)。