发现自 #4813 的实现过程(PR #4874)。**未认领。** 与 #4813 是同一种形状的不同实例。 ## 位置 `packages/core/src/health-monitor.ts`: ```ts /** * Timeout helper */ private timeout< T >(ms: number, message: string): Promise< T > { return new Promise((_, reject) => { setTimeout(() => reject(new Error(message)), ms); // ← 从不 clearTimeout,也不 unref }); } ``` 调用点在 `:112`,健康检查那条 race: ```ts this.timeout(config.timeout, `Health check timeout after ${config.timeout}ms`) ``` 与 #4813 修掉的两处一字不差:守卫 armed 之后就被扔掉,健康检查赢下 race 时那根定时器仍带着 ref 挂满 `config.timeout`。 ## 与 #4813 的差别:今天不发作,但机制相同 #4813 的现象(一次性 CLI 进程空转 120 秒)这里**观察不到**,原因只是**健康监控不在 bootstrap 路径上启动** —— `startMonitoring()` 没有任何调用点在内核启动流程里,所以 `os migrate` 一类的进程根本不会 armed 这根定时器。 也就是说这条今天是**潜伏**的:一旦健康监控真的被接进某个宿主(尤其是周期性检查,每轮一根),它就会变成 #4813 的翻版 —— 而且比 #4813 更糟,因为周期性调用会让孤儿定时器**持续累积**,不是启动时固定的 8 根。 ## 建议的修法 用 #4874 引入的同一个私有 helper 收编,而不是在 `health-monitor.ts` 里再写第三种写法: ```ts try { return await Promise.race([operation, timeoutPromise]); } finally { clearTimeout(guard); } ``` ⚠️ 注意别用 `unref()` —— #4874 实测确认那不是等价方案:unref'd 的守卫在「被守卫的操作永不 settle 且没有别的东西撑着事件循环」时**根本不会触发**,超时被静默吞掉。守卫必须在 race 未决期间保持 ref'd,在落定的那一刻被回收。理由完整写在 `ObjectKernel.raceStartupTimeout()` 的 doc comment 里。 helper 目前是 `ObjectKernel` 的私有方法。收编这条需要先决定它落在哪:提成 `packages/core/src/utils/` 下的共享函数,还是 `HealthMonitor` 各自持有一份。倾向前者 —— 这已经是仓里第三个同形状的超时守卫,再复制一次就该有人把它抽出来了。 ## 边界 - #4813 / PR #4874 的边界写明只碰 `kernel.ts` 的两个守卫,所以那条 PR **没有**动这里,只在正文里记了一笔。 - 不要顺手改 `config.timeout` 的取值 —— 与 #4813 一样,问题不在时长而在没人回收。 Refs #4813(PR #4874)。
Activity
claude commentedon Aug 3, 2026
🔒 认领:PM 循环第 1 轮扩批(维护者要求加速,batch 扩至 5)
会话:
session_01NrmBxj8rK2uGCnh9aipjwX(账号xuyushun441-sys)分支:
claude/issue-4875-health-monitor-timeoutWorktree:
objectstack-issue-4875域:
domain:engine文件面:
packages/core/src/health-monitor.ts及同目录测试⛔ 硬约束:
packages/spec/**及生成物零改动;metadata-protocol/src/protocol.ts零改动(在飞 #4721 占用)。全局在飞不相交已核:#4721、#4636、#4889(rule-validator)、#4821(dataset-executor)、#4884(driver-sql)均与本文件面不交。
Generated by Claude Code
claude commentedon Aug 3, 2026
✅ 验收通过 —— PR #4950(draft,等 CI 绿后入队)
对照实际 diff 复核(3 文件,+209/-9),逐条落实:
timeout(),新raceCheckTimeout()与 fix(core): 插件 init/start 超时守卫在 race 落定时被清除 —— 进程不再空转startupTimeout(#4813) #4874 的raceStartupTimeout()同形(try { await Promise.race } finally { clearTimeout(guard) });拒unref的理由写进了 TSDoc(unref 的守卫不再钉住事件循环的同时也不再是守卫)。executeWithTimeout热路径最值得修)而非顺手扩权;⛔ releases/ 零触碰,changeset 已带。验证:@objectstack/core 465 tests 全过;PR CI 已结算项全绿,Test Core / Dogfood / 类型检查在跑。绿后转 ready 入合并队列。
Generated by Claude Code
fix(core): clear the health-check timeout guard when the race settles (…
fix(service-automation,core): clear the node-execution and shutdown t…
fix(spec): os migrate meta guidance for the ui-* and plugin-* migrati…