fix(worker): 避免慢启动误报消息未接收 - #1050
Conversation
|
你好 @swtxbling 👋 感谢这个 PR!方向我们非常认可:「送达」和「执行」确实是两件事,旧逻辑把三个投递阶段混在一个 2 秒计时器里,冷启动慢时误报「请重发」进而导致重复执行,这个修复切中要害。CI 与本地构建/测试我们都跑过,全绿。 两位 reviewer 独立复审后,有 2 条合前建议(这是自动评审的初步意见,最终以维护者审阅为准): 🟠 建议 1:⏳ 通知发完后投递记录被删除,后续真实失败会静默丢弃(建议合前修复)
用户停留在「请勿重发」,而这条消息永远不会执行。误报的代价是重复执行,但这种静默丢失同样违背本 PR 的初衷。 另外一层:兜底的 还有个附带发现: 建议:⏳ 只是一次「中途播报」,不应终结跟踪。发通知后保留 record(加 🟠 建议 2: 我们做了反向对照:同样的 suspend 场景( 关于 ⏳ 误报率(信息同步,不构成阻断):我们实测了线上 26 个 daemon 日志里 1076 次真实冷启动的 received→commit 间隔,>2000ms 的只有 0.5%,且都最终 ready 成功。另外我们核对了 worker 侧代码:init 的 commit ACK 在同一同步块里紧贴 影响面我们复核过:只动 ordinary-IM 投递跟踪 + i18n 文案,不改 Worker 侧协议; 小结:方向 🟢,建议按上面 2 点修改后合入。再次感谢! |
|
感谢复审,这两点已经在
同时补了 delay 后 late commit、late reject、worker exit,以及「IPC enqueue 已提示后 receipt 晚到不得二次提示」的回归测试。 本地验证:
新的 GitHub CI 已由本次 push 触发,等它跑完后我再确认最终状态。 |
24f63c7 to
6894611
Compare
|
补充确认:后续 CI 已全部通过(build 8m52s,bun-binary 1m11s)。当前分支可合并且工作区干净,等待维护者最终审阅。 |
|
补充确认:最新一轮 CI 已全部通过:
分支已同步最新 |
|
你好 @swtxbling 👋 R2 复审完成 —— 两条建议都已落地,我们独立验证确认真修好了,方向 🟢。 先说结论:这轮已达可合状态,下面只剩 1 条 🟡 非阻断建议(可以顺手补,也可以作为 follow-up)。(这仍是自动评审的初步意见,最终以维护者审阅为准。) ✅ 建议 1:确认修好
我们没有只看新增测试,而是把上一轮暴露缺陷的同一批探针原样重跑:
第三行是我们最在意的一条: 「⏳ → reject → 重投 → 崩溃 → 终态失败通知」整条链路现在都有声了。 ✅ 建议 2:零覆盖已补(一半)新增的 🟡 唯一残留:另一处
|
|
本次 push( 审核发现的两层问题
修法(两层)
测试与验证
CI 由本次 push 触发,跑完后我再确认最终状态。 |
|
补充确认:本次 push( |
|
你好 @swtxbling 👋 R3 复审完成 —— 这轮我们两个 reviewer 都是 0 阻断,而且要先说一句: 你的独立对抗审核推翻了我们上一轮的一个判断,你是对的。 我们 R2 说「 (以下仍是自动评审的初步意见,最终以维护者审阅为准。) 我们独立复核的结论1. 「suspend 时在途 tracked 消息无跨代重投」——证实。 我们两侧分别做了实测与读码,把每条候选重投路径都堵死了:
行为实测也一致:同一 turn 全程只被发送 1 次、never redelivered。所以这条通知是必要且诚实的。 2. 第二层(stale commit ACK)我们认为是这个增量里最有价值的一点。 「结算时仍 pending ⟹ 必然未 commit」确实不成立(fire-and-forget 裸
3. 四处逻辑的反向变异——各自单删都被精准拦红,属实(我们两侧独立跑,结果一致):
第一行正是我们 R2 报的那处零覆盖,现在真被锁住了 —— 那条 🟡 已闭合。 另外 4. 🟡 两条非阻断的记录(都不必卡这轮)① commit message 里 master 那半句不准。 你写「master 同路径至少还发一条(措辞不准的)失败提示」,我们实测 master 是 0 条:把 master 的 准确表述其实对你更有利:不是「恢复一条烂提示」,而是**「master 在这条路径上一直是静默丢失的,本 PR 第一次让它有声」**。建议 squash 时把这半句改准即可。 ② 「commit 已被观测 → 之后才 suspend」这个子窗口仍会静默丢 turn(我们实测确认:commit-then-suspend → 我们判不阻断,理由是它与你已经做过的取舍同构:
所以这条只是让你知道边界存在,作为 follow-up 记录即可。 小结:方向 🟢,R2 的 🟡 已闭合,本轮 0 阻断。 这个增量的质量比前两轮更高 —— 推翻了我们一个判断、补了一层我们没想到的 ACK 竞态、四处逻辑各自有牙、连 source-lock 都是加强的。辛苦了 🙏 |
独立对抗审核发现两层问题: 1. lifecycleRetirement 一律静默结算会吞掉真实丢失--worker 被主动 休眠/更换时,在途的 tracked 消息随 CLI 销毁,没有跨代重投接盘, 用户完全无感。master 在这条路径上本就一直是静默丢失(exit handler 一进来就无条件清除投递记录,到不了任何通知分支),本次 改动让这类丢失第一次有声。 2. "结算时仍在途 => 必然未 commit"不可靠:worker 的 commit ACK 是 fire-and-forget 裸 process.send,而 suspend 先置空 ds.worker、 替换 fork 先推进 generation,晚到的 ACK 会被 stale 门整体丢弃, 已 commit 的 turn 在退出结算时仍显示 pending,断言"一定未执行、 可安全重发"会诱导重复执行。 修法(两层): - stale 门放行退休 worker 自己的 commit ACK:按记录的 worker 对象 身份结算该代的 ordinary delivery(receipt ACK 刻意不放行--它不 具备结算性,吞掉计时器反而会掐死原代的可见失败链;stale worker 不获得任何其它权威) - 退出结算时仍 pending 的记录,主动回收下发诚实的"未确认"通知: 会话被主动休眠或更换,未能确认消息是否进入执行队列,请先查看 会话记录、若未执行再重发(不做无法证明的"一定未执行"断言); 新 i18n key worker.input_retired_unconfirmed(zh/en) - transfer/close/普通 kill 的静默结算语义不变;bmx-recovery-/ meeting-driven/silent-scheduled 沿用 failOrdinaryImDelivery 既有路由 测试: - post-ready 未 commit + suspend -> 一条未确认通知(改原静默断言) - 已 commit 后 suspend -> 静默;late commit ACK(suspend 后/替换 fork 后到达) -> 静默 x2(锁 stale commit 结算路径) - pre-ready 冷启动 init(含 preload receipt)+ suspend -> 恰一条 未确认通知,且非"就绪前退出"误报(锁 start_exited_early 门槛的 && !lifecycleRetirement,此前零覆盖) - en locale 变体 - usage-refresh source-lock 从固定 3000 字节窗口改为语义截取整个 exit handler(handler 增长会把锚点推出固定窗口) 变异验证(四处逻辑分别单独删除,均有测试精准拦红): start 门槛判据 / suppress 判据 / retiredBeforeCommit 通知分支 / stale commit 结算路径。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
42135a7 to
4d90855
Compare
|
R3 的两条 🟡 已处理(本次 push
CI 由本次 push 重新触发,跑完后我再确认最终状态。 |
|
补充确认:本次 push( |
背景
ordinary IM 投递把两个阶段混在了一起:
Worker 忙时,第二阶段的回执可能晚于 2 秒。旧逻辑会在同一 Worker 上重试,并最终提示“未接收,请重发”,但原消息仍可能稍后执行;用户重发会生成新的
turn_id,从而造成重复执行。Closes #1018
改动
received/committedACK。验证
npx --yes bun@1.4.0 run buildnode_modules/.bin/tsc --noEmitnode_modules/.bin/tsc -p tsconfig.scripts.json --noEmitmaster对照:上述 21 个失败数量和原因完全一致,均为既有环境/平台失败git diff --check边界
本 PR 只修正投递状态语义和误导性重试,不新增父进程持久队列、全局冷启动并发限制、跨 Worker generation 自动重投,也不修改 Bun standalone 的 preload 传递;这些应作为独立问题评估。
已知残留窗口(R3 复审确认不阻断,作为 follow-up 记录):
lastScreenStatus === 'idle'闸,正在产出的会话不会被回收),窗口只有 commit→flush 量级;堵它需要给「已 commit 未产出」设超时,会重新引入本 PR 要消灭的误报类别(真慢 vs 假挂死无法区分),故不在本 PR 处理。