Skip to content

fix(dashboard): SSE 建连回放本进程关闭的会话,修 restore 期 zombie row 看板丢失/残留 stale-active#281

Open
deepcoldy wants to merge 1 commit into
masterfrom
fix/sse-replay-closed-zombie-rows
Open

fix(dashboard): SSE 建连回放本进程关闭的会话,修 restore 期 zombie row 看板丢失/残留 stale-active#281
deepcoldy wants to merge 1 commit into
masterfrom
fix/sse-replay-closed-zombie-rows

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

背景

#277(已合并)的 follow-up,处理 Codex 二审第三轮发现的残留时序洞。

#277GET /api/events 建连时先 subscribe 再回放当前活跃会话快照,确定性修住了「daemon 重启后仍 active 的恢复会话在看板消失」。但那次回放只遍历 active Map,没覆盖 restore 期间被关闭的 zombie 会话

问题

restore 期间,一个「backing pane 单独死掉」的 zombie 会话会被 restoreActiveSessions():注册进 active Map → announceSessionRow() 广播 session.spawned → 立刻 probe 到 missingcloseSession()(从 active Map 删除、置 store 行 closed)。

如果一个 dashboard 恰好在 daemon「发布发现 descriptor(restore 之前)→ restore 完成」这个窗口里重连:

  • 它的初始 GET /api/sessions hydrate 在 restore 前发生,拿到的 active 为空、该 zombie 当时还是 status=active 不在 closed rows 里 → 完全没拿到
  • 上面那两个事件(spawned + close)都早于它的 SSE 订阅建立 → DashboardEventBus 无 buffer 全丢;
  • 等它 SSE 连上,zombie 已不在 active Map → [Fix] fix dashboard restored session rows #277 的 active-only 回放补不到它。

结果:新建 aggregator 永远看不到它;若该 dashboard 进程原本缓存它为 activehydrateSessions() 只 upsert 不删 absent 行 → 残留一条 stale active(已死会话在看板显示成活的)。

改动

/api/events 的 snapshot-on-connect 在回放活跃会话后,再回放「closedAt ≥ 进程启动时刻 的已关会话」composeRowFromClosedsession.spawned 发出):

  • 进程启动时刻过滤 = 只补本次 run 内发生的关闭(正好覆盖 restore 期 zombie 与本 run 内任何关闭),不回放整个 closed 历史——完整历史本就由 GET /api/sessions hydrate 提供。
  • session.spawned(而非 session.update):目标行可能对该客户端未知,update 会被当 unknown row 丢;两端(aggregator / 前端 store)均按 sessionId upsert,closed 行的 status:'closed' 会覆盖任何 stale active 条目,stale-active 一并修掉。
  • active 行已收集进 activeIds,跳过避免与活跃会话重复。

验证

  • pnpm exec tsc --noEmit 绿。
  • 新增回归测试:注册表为空(zombie 已被 evict)+ store 里有本 run 关闭的会话 → 连 /api/events 仍收到该会话的 session.spawnedstatus==='closed'closedAt 为数字)。
  • 全量 pnpm test:4946 passed,22 failed 全是已知 env 失败(workflow-cli* / whiteboard-cli / preset-export-cli / workflow-c0-isolation,CLI 子进程测试,与本 PR 无关)。

…ctive

#277 的 follow-up(Codex 二审第三轮发现)。

#277 让 /api/events 建连先 subscribe 再回放当前活跃会话,确定性修住了「恢复后仍
active」的行;但只遍历 active Map。restore 期间的 zombie(backing pane 单独死掉)
是「先 announceSessionRow 再立刻 closeSession 从 Map 删除」,这俩事件都早于一个正
在 descriptor→restore 窗口里重连的 dashboard 的 SSE 订阅 → 全丢;等 SSE 连上时
zombie 已不在 active Map,active-only 回放补不到它(它此刻是 closed row)。新建
aggregator 永远看不到它;若 dashboard 原本缓存它为 active,hydrateSessions 只
upsert 不删 absent → 残留 stale active(已死会话显示成活的)。

修:snapshot-on-connect 在回放活跃会话后,再回放「closedAt ≥ 进程启动时刻」的已关
会话(composeRowFromClosed 作 session.spawned)。按进程启动时刻过滤 = 只补本 run
的关闭(含 restore 期 zombie),不回放整个 closed 历史(那本就由 GET /api/sessions
hydrate 提供)。用 session.spawned 因目标行可能未知,两端按 sessionId upsert,closed
行的 status 覆盖任何 stale active 条目。补回归测试。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant