Skip to content

v0.28.2 — 大会话冷读修复 / Large-Session Cold-Read Fix

Choose a tag to compare

@Menghuan1918 Menghuan1918 released this 27 Oct 15:08
· 120 commits to main since this release

安装 / Installation

要求 DSH 0.2.1-rc.1 或更新的兼容 0.2.x 版本。 / Requires DSH 0.2.1-rc.1 or a newer compatible 0.2.x release.

dsh plugin --profile web add dsh-better-sidebar@0.28.2

将 web 换成你实际使用的 profile 名。安装或更新后,按 Cmd/Ctrl + Shift + R 硬刷新浏览器。
Replace web with your profile name. After installing or updating, hard-refresh the browser with Cmd/Ctrl + Shift + R.

DSH 版本 / DSH version 插件版本 / Plugin version
0.2.1-rc.1+(兼容 0.2.x / compatible 0.2.x) 0.28.2
0.2.1-alpha.5 0.28.0-alpha.0
0.2.0-rc.2 0.26.1
0.2.0-rc.1 0.24.1
0.1.7-rc.1 至 / through 0.1.7 0.22.1

中文|相比 v0.28.1

🛡 大会话冷读修复(OOM 根因)

0.28.1 的同键单飞只覆盖 /sidebar/api,而启动期真正的并发扇出不走那条路:fs-watch 每个被监视目录一帧(服务端在判断 op 之前就解析 cwd)、/sidebar/file 与 /sidebar/html 每个资源一次、upload / write 每次调用一次。这些入口各自调 sessionCwdOf(),而它此前对 detached 会话调 sessionQuery.observeSession(..., { projectionMode: 'all' }) —— 为拿一个 workingDirectory 要解码整份日志并水合全部投影。39 MB 级会话因此仍被并发整读数十次:0.28.0 的 44 次 OOM 与 0.28.1 的 36 次是同一条路径,不是「去重失效」。

现在 detached 会话的 cwd 改读存储 header 里的工作区地址:sessionPersistence.stat() 只读第一个 zstd 帧(header-only,绝不解码日志),并按会话 id 做进程内缓存(header 不可变)与在飞去重;activeWorktreeRootOf() 对 detached 会话直接返回该地址,不再折持久化日志。已 attach 的会话行为不变,仍跟随 working_directory 与 linked worktree。

  • 代价:会话 attach 之前,侧边栏根是原始项目目录而非 agent 最后所在的子目录 / worktree;attach 后下一次 5 s 轮询自动切回。
  • 实测(40,157,518 字节会话,本机重建):12 个 session.cwd + 36 个绕过入口调用并发 → 1 次 header 读、0 次整读、3 ms、RSS +0.3 MB;同产物用修复前代码跑 6 个绕过入口 → 6 次整读、峰值 +1002 MB(外推 36 次 ≈ 6.0 GB,与事故 6.2 GB 峰值吻合)。单次整读成本 467 ms / +501.6 MB。

🧪 测试与门禁

  • tests/smoke.spec.ts 重写该组 8 例:header 优先于陈旧提示、header 每会话只读一次、12 并发共享一次 header 读、无 stat / stat 失败回落客户端提示、live committed 目录优先、相对 cwd 仍拒绝为 bad-request、defaultDirectory 与进程 cwd 兜底。
  • tests/active-worktree.spec.ts 新增 1 例,钉住「detached 会话不做持久化读、不 spawn git」。
  • 机制、取舍与完整证据:设计记录。

English|Changes since v0.28.1

🛡 Large-session cold-read fix (OOM root cause)

The 0.28.1 same-key single-flight only covered /sidebar/api, but the startup fan-out never went through it: the fs-watch socket sends one frame per watched directory (the server resolves cwd before it even reads the op), /sidebar/file and /sidebar/html resolve cwd per resource, and upload / write per call. Each of those paths calls sessionCwdOf(), which for a detached session ran sessionQuery.observeSession(..., { projectionMode: 'all' }) — decoding the whole log and hydrating every projection to answer with one workingDirectory string. A 39 MB session was therefore still fully decoded dozens of times in parallel: the 44-read OOM on 0.28.0 and the 36 reads seen on 0.28.1 are the same path, not a broken dedupe.

A detached session's cwd now comes from the workspace address stored in its header: sessionPersistence.stat() reads only the first zstd frame (header-only, never the log), memoized per session id (headers are immutable) with same-session in-flight sharing; activeWorktreeRootOf() answers a detached session with that address instead of folding the stored log. Attached sessions are unchanged and still follow working_directory and linked worktrees.

  • Cost: until the session is attached, the sidebar roots at the original project rather than the agent's last directory/worktree; the next 5 s poll switches back after attach.
  • Measured (40,157,518-byte session, rebuilt locally): 12 session.cwd + 36 bypass-entry calls concurrently → 1 header read, 0 full reads, 3 ms, +0.3 MB RSS; the same artifact on the pre-fix build with 6 bypass calls → 6 full reads, +1002 MB peak (≈6.0 GB extrapolated for 36, matching the 6.2 GB incident peak). One full read costs 467 ms / +501.6 MB.

🧪 Tests and gates

  • tests/smoke.spec.ts rewrote that group of 8 cases: header ahead of a stale hint, one header read per session, 12 concurrent asks share one, no stat / failing stat falls back to the client hint, the live committed directory wins, a relative cwd is still refused as bad-request, defaultDirectory and process-cwd fallbacks.
  • tests/active-worktree.spec.ts gained one case pinning “a detached session performs no persistence read and no git probe”.
  • Rationale, trade-offs and full evidence: design record.

贡献者与致谢 / Contributors & thanks

维护者 @Menghuan1918 定位并修复了 detached 会话 cwd 的整份日志读路径,完成 40 MB 会话基准与本版发布。也感谢报告该问题的测试方,以及所有提交反馈和参与试用的用户。
Maintainer @Menghuan1918 traced and fixed the detached-session cwd full-log read path, ran the 40 MB benchmark and shipped this release. Thanks also to the tester who reported the incident, and to everyone who shared feedback or tested the release.


完整变更 / Full changelog: CHANGELOG.md · CHANGELOG_EN.md