Repository navigation
[Bug] 超大会话全量重写 transcript,导致主进程周期性无响应(0.20.1 / 383 MB 会话) #1836
Copy link
Copy link
Open
Description
What happened? / 问题描述
PI-Desktop 0.20.1(Windows 11 x64,Electron 43.6.0)在长任务/超大会话中会周期性“假死”:窗口 2 秒以上不响应,无法点击或切换项目,但后台任务仍在执行;重启后恢复,但下一次大会话继续复现。没有 renderer crash / .dmp / Windows Application Hang 记录,属于 I/O 与锁竞争导致的假死,不是崩溃。
本机复现并抓到的量化证据:
<data-dir>/sessions/<session-id>.jsonl达到 383.9 MB,该会话 15,786 条消息;其中 54 行超过 1 MB,52 行包含 base64/PNG 数据特征(主要是Read读取截图的 tool result)。pi-desktop-host-core在 2026-10-23 14:49:48、14:54:54 各出现一次 约 383 MB 的单次写盘;同一文件大小不变、mtime 变化,说明走的是整份 transcript 重写路径,而不是 append。- 14:54:59、14:55:01 对主窗口执行
SendMessageTimeout(WM_NULL, 2000ms)连续超时;同一时刻主进程 RSS 峰值约 1.6 GB。 - 整个
sessions目录:100 个会话 / 2.37 GB,其中 8 个会话超过 75 MB。 - 启动阶段也出现过
host RPC capacity is exhausted(应用启动约 17 秒后),并伴随大量.layout重建;强制退出后下一次启动更容易触发。
源码侧与 0.20.1 的实现对应如下:
crates/host-core/src/sessions.rs:2750replace_messages()→sessions.rs:2781rewrite_transcript_body()→crates/host-core/src/transcripts.rs:1010write_transcript_with_compactions():创建<session-id>.jsonl.tmp,重写全部 records,然后 rename 覆盖原文件。crates/host-core/src/sessions.rs:2999update_tool_review_state()只修改一个 tool result 的review.state,也会调用replace_messages(),即整份 transcript 全量重写。- 对比
transcripts.rs:720的append_line()是正常 append;问题集中在“非追加”的重写路径。
Steps to reproduce / 复现步骤
- 用
Read读取多张 4–8 MB 的 PNG,或让视觉/截图类 agent 持续产生 base64 图片,把单个会话养到 300 MB 以上。 - 在同一实例里同时跑多个长会话(本机复现时为 6 个会话同时 running)。
- 让大会话继续产生 tool 事件,或触发 review 状态更新、编辑、删除、重试等操作。
- 观察
sessions/<id>.jsonl:大小不变但 mtime 每次更新;与此同时 UI 出现 2 秒以上不响应。
Expected behavior / 预期行为
- 普通 append、tool 结果、review 状态更新不应重写整份 transcript。
- 大会话的写盘不应阻塞主进程/UI;超过体积阈值时应明确提示并支持分段、归档或 fork。
session.list/启动索引不应与交互 RPC 争抢同一把全局锁或同一批 worker。
Actual behavior / 实际行为
- 单次约 383 MB 的写盘与 UI 2 秒超时重合,卡死可重复发生。
- 日志出现
host RPC capacity is exhausted、session.persistence.flush.paused。 - 没有崩溃转储;重启只能临时恢复,无法根治。
建议的修复方向
- 局部更新替代整包重写:为
update_tool_review_state、消息编辑/删除、revision 激活等增加按message id / seq的增量 RPC;不要为单个 review 字段调用replace_messages()。 replaceMessages分块/流式化:按 id/seq 做 diff,只重写变化的后缀;把临时文件写入放到 blocking 线程,避免持有全局AppState锁做整文件 I/O。- 增加单会话体积护栏:超过 64/128 MB 时提示 fork / compact / 新会话;支持 transcript 分段,而不是无限增长。
- 附件化大内容:
Read图片和超大 tool output 存为附件文件,transcript 只存引用和缩略图;本机 383 MB 文件的主要来源就是 52 条 base64 截图行。 - 启动索引后台化:
session.list的.layout重建放后台并限制并发,优先返回缓存投影(可参考 perf(sessions): cache file-derived session summaries by file identity #1796)。 - RPC 公平性与背压:按插件/调用方配额,支持取消;
HOST_OVERLOADED不应被误分类为 provider auth error(参考 [Bug] host-core 卡死后 RPC 槽位不释放,32 个槽位被占满,所有会话报「API key auth failed … host RPC capacity is exhausted」,重启前一直无法恢复 #1071)。 - 可观测性:host 日志按 method + payload size 记录慢 RPC;对超过阈值的 session 打 warning,方便用户提前发现“大会话”。
相关: #1366(长任务 UI 卡住)、 #211(replaceMessages 整包重写 transcript)、 #1071(RPC 槽位耗尽)、 #1796(session.list 缓存)。
App version / 应用版本
0.20.1
Operating system / 操作系统
Windows
Extra environment / 其他环境信息
Windows 11 x64(10.0.26100)· Electron 43.6.0 · protocol 11 · host 0.20.1 · NSIS 安装 · 16 GB RAM · NVMe SSD。复现时还有第三方插件高频调用 session/list / session/get,会放大 RPC 压力;但上述 383 MB 写盘发生在 host-core 的 transcript 重写路径上,属于核心问题。
Logs / 日志
# host-core 单次写入增量(与 383.9 MB 的 session 文件大小一致)
2026-10-23 14:49:48 write ≈ 383 MB
2026-10-23 14:54:54 write ≈ 383 MB
# 主窗口无响应探测(WM_NULL / SendMessageTimeout,超时 2000 ms)
2026-10-23 14:54:59 TIMEOUT
2026-10-23 14:55:01 TIMEOUT
# 同期的持久化/RPC 告警
{"level":"warn","category":"persistence","event":"session.persistence.flush.paused","data":"Error: host RPC capacity is exhausted"}
{"level":"warn","category":"diagnostics","event":"tray.session.refresh.failed","data":"Error: host RPC capacity is exhausted"}
{"level":"warn","category":"lifecycle","event":"native.window.corner.preference.failed","data":"Error: host RPC timeout: window.setNativeCornerPreference"}
Screenshots / 截图
No response
Reacted by Lan
Metadata
Metadata
Assignees
Labels
No labels
Activity
vastsa commentedon Oct 23, 2026
已发现此问题,目前暂未稳定复现,正在研究中,感谢反馈
736368217 commentedon Oct 23, 2026
老大,能给我的pr也审一下不 #1758 ,谢谢
vastsa commentedon Oct 23, 2026
您好,ui 辛苦重新设计一下,放在这里显得有点重
test(storage): pin review state rewrite and append ordering
muzimu217 commentedon Oct 28, 2026
把当前
main(b91f35122)上仍会整份重写 transcript 的入口清点了一遍,可以缩小「什么操作会触发那 383 MB 写盘」的范围。rewrite_transcript/replace_messages的生产调用点只有四处:crates/host-core/src/sessions.rs:2965(truncate_from路径)。这是最常见的用户操作,也是唯一在正常使用中会周期性触发整份重写的地方。sessions.rs:3090(update_tool_review_state→replace_messages)。点一次就重写整个 body;PR fix(storage): append review state without rewriting history #1956 正在把它改成追加tool_review_state记录。session.replaceMessagesRPC ——crates/host-core/src/rpc/mod.rs:3026。它出现在 sidecar 的宿主代理白名单里(packages/host-runtime/src/agent-sidecar.ts:58),但 vendored 的@earendil-works/*与patches/*.patch都不调用它,所以实际入口是受信扩展(spec 16 §10.1 的 ExtensionAPI)。data_relocation.rs:275,一次性操作,与长会话无关。结论:正常流式输出本身不重写 transcript(追加路径是
sessions.rs:2153/:2530的append_record)。所以大会话里的周期性假死最可能对应 regenerate/truncate,或扩展调用了replaceMessages;#1956 合入后第 2 条也会消失。如果要复现,建议在 383 MB 那个会话上只做一个动作——点一次「重新生成」——然后看
pi-desktop-host-core是否出现与文件等大的单次写盘。这比等它自己发生要可控。fix(storage): append review state without rewriting history