mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
fix(guard): 心跳提前启动 + 首次同步写入 —— 启动竞态的 worker 侧根修
562ba6bd 修了 guard 侧(给旧 mtime 加启动宽限,不误杀),那只是防御。
**根因**在 worker 侧:心跳文件要等全部重型初始化完(原 main 尾部)才
首写,且首次写是异步(在 goroutine 里,与多模态空间/知识库重建竞争
调度)。停机 >30s 后重启时 mtime 停在上一代 worker,guard 的 5s ticker
先到就 SIGKILL。
两层独立生效:
worker 侧(本提交,治本):dataDir 一确定就启动心跳,首次写同步
guard 侧(562ba6bd,防御):保留启动宽限期容忍旧 mtime
## 改动
- main.go:startHeartbeat 从尾部(190)移到 dataDir 解析后(54),
用 context.Background()(心跳生命周期=进程,与 agent ctx 解耦)。
- bootstrap.go:startHeartbeat 首次 writeHB() 提到 goroutine 之外
同步执行——返回前 mtime 必已变新,main 往下做慢事之前 guard 看到的
是新心跳。
## 判据(TestHeartbeatFirstWriteSynchronous,变异测试通过)
(a) 返回后立即查 mtime:必须是新的(同步首写),不许等到第一个 tick
首写移回 goroutine(异步)→ (a) 当场 FAIL,已实测
(b) 5s 后 mtime 再变(ticker 在跑)
(c) stop() 后不再更新(hbStop 生效,不泄漏 goroutine)
判据覆盖完整生命周期:启动即新 → 周期更新 → 停止即停。
cmd/homed 包全量测试通过;全仓生产编译 GREEN。
This commit is contained in:
@ -53,6 +53,23 @@ func main() {
|
||||
opt.dataDir = resolveDataDir(opt.dataDir)
|
||||
}
|
||||
|
||||
// ---- 心跳:dataDir 一确定就启动(★ 启动竞态的 worker 侧根修)----
|
||||
//
|
||||
// 心跳文件是 guard 判 worker 存活的唯一依据(mtime 新鲜度 + IPC PING)。
|
||||
// 必须在**任何重型初始化之前**首写同步落盘:停机 >heartbeatTimeout 后重启,
|
||||
// 文件 mtime 停在上一代 worker(已过期);若等所有初始化完才首写(原位置在
|
||||
// main 尾部),guard 的 5s ticker 先到、拿旧 mtime 判 stale → 把还在加载
|
||||
// 多模态空间/知识库的 worker SIGKILL(2026-10-12 生产实测:6 连杀 +
|
||||
// failback 降级,详见 562ba6bd)。
|
||||
//
|
||||
// guard 侧另有启动宽限期兜底(shouldKillStaleHeartbeat),两层独立生效:
|
||||
// worker 侧让 mtime 尽快变新(治本),guard 侧容忍旧 mtime(防御)。
|
||||
//
|
||||
// 用 context.Background()(而非 main 的 ctx):心跳生命周期 = 进程,
|
||||
// 与 agent 的 ctx 解耦;停止靠本函数返回的 hbStop(waitForShutdown 调它)。
|
||||
stopHeartbeatEarly := startHeartbeat(opt.dataDir, context.Background())
|
||||
defer stopHeartbeatEarly()
|
||||
|
||||
if opt.cliSocket == "" {
|
||||
opt.cliSocket = filepath.Join(opt.dataDir, "cli.sock")
|
||||
}
|
||||
@ -186,7 +203,7 @@ func main() {
|
||||
log.Printf("[homed] kernel ready, waiting for plugin IO...")
|
||||
|
||||
// ---- 等待退出信号 ----
|
||||
|
||||
stopHeartbeat := startHeartbeat(opt.dataDir, ctx)
|
||||
waitForShutdown(ctx, opt.dataDir, restartCh, stopHeartbeat, pluginReg, trk, cfgReg, sup)
|
||||
//
|
||||
// 心跳已在 main 早期启动(见文件头部 startHeartbeatEarly),此处不再重复启动。
|
||||
waitForShutdown(ctx, opt.dataDir, restartCh, stopHeartbeatEarly, pluginReg, trk, cfgReg, sup)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user