Repository navigation
长会话内存增长的两条通道:request/header 重复写入、workspace-changes 快照无回收(第二条已附 PR) #13155
chenxixian
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
长会话内存增长的两条通道:
request/header重复写入、workspace-changes快照无回收其中第二条我已经提交修复 PR:chenxixian#3 (基线
dsh-v0.2.2-rc.2,10 文件以内,附测试)。第一条属于日志格式语义变更,不适合直接给 patch,先在这里说明机制并提建议。环境
0.2.0-rc.2(首次崩溃)/0.2.1-rc.5(复核环境)v24.20.0(arm64)26.6.2,Mac17,3(M5),16 GBdsh web --no-open(未设置NODE_OPTIONS)两次进程级崩溃,都是 V8 老生代耗尽后
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory:node -e "require('v8').getHeapStatistics().heap_size_limit"在 16 GB 机器上实测 4288 MB。桌面版已经通过 PR dsh-tauri#703 下发--max-old-space-size,dsh web目前没有(dsh-app-boot@0.2.2-rc.2无相关代码)。问题 1:
request/header的去重粒度失效真实会话(解压 220 MB / 65,540 帧)的事件构成:
assistant/messagerequest/headersystem/messagetool/resulttool/calluser/message按
reason拆开 1,032 条request/header:reasonserieschangeresumeinitial对应
packages/core/agent-loop/src/agent.ts的buildRequest():headerEquals(baseline, header)的去重只在同一 series 内有效;一旦startsSeries为真(每轮交互几乎都会开启新 series),代码走else if (startsSeries)分支,无条件把完整 header 再写一遍,尽管此时headerEquals判定它与 baseline 完全相同。所以问题不是「没做去重」,而是「去重粒度对不上实际交互节奏」。后果是双层的:磁盘上一份会话被这些重复帧撑到 220 MB;内存里每一份都会成为独立的 JS 对象并被 snapshot 整体持有(实测物化系数 2.1×,约 4,696 字节/帧;190 MB 解压会话常驻约 408 MB),同时每次请求还要把整个 transcript 再序列化一次——第二次崩溃的栈正是
Runtime_StringSplit→Factory::NewArray分配失败。建议:
reason: "series"的帧不重复携带完整 header,只携带对上一份 header 的引用(内容哈希 / 上一帧 seq),或者只带差异。这能把它在该会话里的占比从 31.9% 压到不足 3%,同时消除每次请求的重复反序列化。我知道这会改日志格式语义,所以先提出来讨论,不塞进 PR。同类问题在 openai/codex#30932("huge rollout dominated by repeated compacted records" 导致 resume 时 OOM)已经出现过。
问题 2:
dsh-workspace-changes-*快照无回收 → 已提 PRstat -f '%l'→ links=1,实体副本而非硬链接fseventsd常驻内存killall fseventsd后回落 4.6 MB)原因很直接:scratch 目录只在 Session disposal 时删除,而 OOM / crash / kill 的 Host 永远不会 dispose,于是这些目录在临时根目录里永久堆积,没有 TTL 也没有容量上限。
PR chenxixian#3 的做法:scratch 目录创建时写一个写明所属进程的
lease文件;插件加载时扫描临时根目录,所属进程已退出的目录一律回收,没有 lease 的(旧版本写出的)按新的orphanTtlMs(默认一周)回收,所属进程仍存活的一律不动,所以并发 Host / Session 共用临时目录也安全。pnpm vitest run packages/deliverables/workspace-changes通过 10 文件 / 65 测试。这和
0.2.2-rc.2已经引入的「归档会话默认清理符合条件的 Git 工作树」是同一个形状——「teardown/reload 时回收」这个机制只是没覆盖到 scratch 目录。fseventsd被 inode 事件风暴撑大这件事另有同类案例:abiosoft/colima#1569、Apple Developer Forums #849819。在 16 GB 机器上,4.3 GB JS 堆 + 2.4 GB fseventsd 会把物理内存吃到只剩 30%,反过来让 OOM 更早发生,而 OOM 又正是产生这些残留的原因——一个闭环。第三条建议(不打算单独提 PR)
dsh web也下发--max-old-space-size,直接复用桌面版 PR dsh-tauri#703 的策略(物理内存的一半、钳到[2048, 8192]、NODE_OPTIONS优先)。现在 web 模式下用户必须自己设环境变量,否则会撞在 4288 MB 的默认上限。顺带一个更省事的告警点:会话解压体积超过阈值(比如 100 MB)时,在加载时打一行 warn 或给 UI 提示「该会话已超健康体积,建议拆分」,把发现时间从崩溃提前到崩溃前几小时。
本机对账摘要
request/headerseries932 帧(90.3%)system/messagefseventsd峰值 / 清理后数据来自本机实测(
zstd -dc、vmmap、top、stat、du),会话内容已脱敏,只提供计数与字节数。All reactions