发现自 #4813 的实现过程(PR #4874)。未认领。 与 #4813 是同一种形状的不同实例。
位置
packages/core/src/health-monitor.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:
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 里再写第三种写法:
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 各自持有一份。倾向前者 —— 这已经是仓里第三个同形状的超时守卫,再复制一次就该有人把它抽出来了。
边界
Refs #4813(PR #4874)。
发现自 #4813 的实现过程(PR #4874)。未认领。 与 #4813 是同一种形状的不同实例。
位置
packages/core/src/health-monitor.ts:调用点在
:112,健康检查那条 race:与 #4813 修掉的两处一字不差:守卫 armed 之后就被扔掉,健康检查赢下 race 时那根定时器仍带着 ref 挂满
config.timeout。与 #4813 的差别:今天不发作,但机制相同
#4813 的现象(一次性 CLI 进程空转 120 秒)这里观察不到,原因只是健康监控不在 bootstrap 路径上启动 ——
startMonitoring()没有任何调用点在内核启动流程里,所以os migrate一类的进程根本不会 armed 这根定时器。也就是说这条今天是潜伏的:一旦健康监控真的被接进某个宿主(尤其是周期性检查,每轮一根),它就会变成 #4813 的翻版 —— 而且比 #4813 更糟,因为周期性调用会让孤儿定时器持续累积,不是启动时固定的 8 根。
建议的修法
用 #4874 引入的同一个私有 helper 收编,而不是在
health-monitor.ts里再写第三种写法:unref()—— #4874 实测确认那不是等价方案:unref'd 的守卫在「被守卫的操作永不 settle 且没有别的东西撑着事件循环」时根本不会触发,超时被静默吞掉。守卫必须在 race 未决期间保持 ref'd,在落定的那一刻被回收。理由完整写在ObjectKernel.raceStartupTimeout()的 doc comment 里。helper 目前是
ObjectKernel的私有方法。收编这条需要先决定它落在哪:提成packages/core/src/utils/下的共享函数,还是HealthMonitor各自持有一份。倾向前者 —— 这已经是仓里第三个同形状的超时守卫,再复制一次就该有人把它抽出来了。边界
startupTimeout(#4813) #4874 的边界写明只碰kernel.ts的两个守卫,所以那条 PR 没有动这里,只在正文里记了一笔。config.timeout的取值 —— 与 内核的插件 init/start 超时守卫定时器从不清除也不 unref —— 每个进程在工作结束后还要空转 startupTimeout(CLI 挂 ~120s 才退出) #4813 一样,问题不在时长而在没人回收。Refs #4813(PR #4874)。