Skip to content

[Bug] 超大会话全量重写 transcript,导致主进程周期性无响应(0.20.1 / 383 MB 会话) #1836

Open

Description

@736368217

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:2750 replace_messages() → sessions.rs:2781 rewrite_transcript_body() → crates/host-core/src/transcripts.rs:1010 write_transcript_with_compactions():创建 <session-id>.jsonl.tmp,重写全部 records,然后 rename 覆盖原文件。
  • crates/host-core/src/sessions.rs:2999 update_tool_review_state() 只修改一个 tool result 的 review.state,也会调用 replace_messages(),即整份 transcript 全量重写。
  • 对比 transcripts.rs:720 的 append_line() 是正常 append;问题集中在“非追加”的重写路径。

Steps to reproduce / 复现步骤

  1. 用 Read 读取多张 4–8 MB 的 PNG,或让视觉/截图类 agent 持续产生 base64 图片,把单个会话养到 300 MB 以上。
  2. 在同一实例里同时跑多个长会话(本机复现时为 6 个会话同时 running)。
  3. 让大会话继续产生 tool 事件,或触发 review 状态更新、编辑、删除、重试等操作。
  4. 观察 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。
  • 没有崩溃转储;重启只能临时恢复,无法根治。

建议的修复方向

  1. 局部更新替代整包重写:为 update_tool_review_state、消息编辑/删除、revision 激活等增加按 message id / seq 的增量 RPC;不要为单个 review 字段调用 replace_messages()。
  2. replaceMessages 分块/流式化:按 id/seq 做 diff,只重写变化的后缀;把临时文件写入放到 blocking 线程,避免持有全局 AppState 锁做整文件 I/O。
  3. 增加单会话体积护栏:超过 64/128 MB 时提示 fork / compact / 新会话;支持 transcript 分段,而不是无限增长。
  4. 附件化大内容:Read 图片和超大 tool output 存为附件文件,transcript 只存引用和缩略图;本机 383 MB 文件的主要来源就是 52 条 base64 截图行。
  5. 启动索引后台化:session.list 的 .layout 重建放后台并限制并发,优先返回缓存投影(可参考 perf(sessions): cache file-derived session summaries by file identity #1796)。
  6. RPC 公平性与背压:按插件/调用方配额,支持取消;HOST_OVERLOADED 不应被误分类为 provider auth error(参考 [Bug] host-core 卡死后 RPC 槽位不释放,32 个槽位被占满,所有会话报「API key auth failed … host RPC capacity is exhausted」,重启前一直无法恢复 #1071)。
  7. 可观测性: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

Activity

  1. vastsa commented on Oct 23, 2026

    @vastsa
    Owner

    已发现此问题,目前暂未稳定复现,正在研究中,感谢反馈

  2. 736368217 commented on Oct 23, 2026

    @736368217
    Author
    1. 已发现此问题,目前暂未稳定复现,正在研究中,感谢反馈

    老大,能给我的pr也审一下不 #1758 ,谢谢

  3. vastsa commented on Oct 23, 2026

    @vastsa
    Owner
    1. 已发现此问题,目前暂未稳定复现,正在研究中,感谢反馈

    老大,能给我的pr也审一下不 #1758 ,谢谢

    您好,ui 辛苦重新设计一下,放在这里显得有点重

  4. muzimu217 commented on Oct 28, 2026

    @muzimu217
    Contributor

    把当前 main(b91f35122)上仍会整份重写 transcript 的入口清点了一遍,可以缩小「什么操作会触发那 383 MB 写盘」的范围。

    rewrite_transcript / replace_messages 的生产调用点只有四处:

    1. regenerate / truncate —— crates/host-core/src/sessions.rs:2965(truncate_from 路径)。这是最常见的用户操作,也是唯一在正常使用中会周期性触发整份重写的地方。
    2. 回滚 review 卡片 —— sessions.rs:3090(update_tool_review_state → replace_messages)。点一次就重写整个 body;PR fix(storage): append review state without rewriting history #1956 正在把它改成追加 tool_review_state 记录。
    3. session.replaceMessages RPC —— 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)。
    4. 数据目录迁移 —— data_relocation.rs:275,一次性操作,与长会话无关。

    结论:正常流式输出本身不重写 transcript(追加路径是 sessions.rs:2153/:2530 的 append_record)。所以大会话里的周期性假死最可能对应 regenerate/truncate,或扩展调用了 replaceMessages;#1956 合入后第 2 条也会消失。

    如果要复现,建议在 383 MB 那个会话上只做一个动作——点一次「重新生成」——然后看 pi-desktop-host-core 是否出现与文件等大的单次写盘。这比等它自己发生要可控。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions