跳转至

保留的终端模式扫描尾部

kitty 键盘跟踪器和守护进程 mouse 模式镜像会保留一个最多 4,096 个 UTF-16 码元的未完成转义序列尾部。V8 切片字符串可能通过该小尾部使整个已消费输入保持存活。一个普通的拆分分组模式序列 ESC[?1049;2004;1000; 就足以做到:其 18 字符尾部会在解析器保持空闲时保留每个输入后备字符串。

该修正仅通过现有的 ownRetainedString 辅助函数复制被接受的未完成尾部。空/被拒绝的尾部以及短 ESC/CSI 前缀保持现有行为;该辅助函数不会改动短于 13 个码元的字符串。解析器状态、实时/重放语义、栈上限、模式标志和线路内容均保持不变。这些是 #20960 中的额外边界,与 PTY 检测器携带 并列。

复现

ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/terminal-mode-tail-retention/reproduce.cjs

使用已安装的 Electron 可执行文件运行同一脚本,设置 ELECTRON_RUN_AS_NODE=1 和 ORCA_BACKGROUND_LAUNCH=1,并传入相同的 Node 标志。这不会启动任何应用窗口或原生 PTY。每次运行都有 30 秒截止时间,并写入 Node 结果 或 Electron 结果。

加载器读取实际的五个源模块,验证其固定哈希,仅为基线在内存中反转三个复制调用/导入,并验证得到的基线哈希。所有被评估的模块和 bundle 哈希都会被记录。它不需要 Git 历史、绝对开发路径或被忽略的笔记。CRLF 源文本在哈希前会被规范化。解析器和标志解析器匹配 v1.4.198;除这些复制调用外,所有五个模块都匹配扩展前主题 8d599520e44654a5c28e9930e3070c00d6499931。这测试的是当前依赖项和所选源模块,而不是历史应用二进制文件。参见 源版本。

每个运行时检查 42 个有界堆用例:基线、固定 Buffer 复制器和固定 Bufferless 复制器;kitty 实时/重放、mouse 实时;32 个不同的 64-Ki 字符输入和 8 个 4-Mi 字符输入;短、完整、超大以及 C1-CSI 尾部对照。完成时必须重建正确的模式并释放大后备字符串。额外对照保持重放推送幂等性、16 帧实时栈上限、备用屏幕状态、快照未知性、mouse 编码,以及带有尾部不完整序列的 RIS。

两个运行时均通过全部 42 个用例。代表性实时路径堆增量(字节):

运行时 解析器 输入 × 所有者 基线 Buffer 复制 Bufferless 复制
Node 26 Kitty 64 Ki × 32 2,120,952 18,376 9,480
Node 26 Mouse 64 Ki × 32 2,111,984 15,112 9,336
Node 26 Kitty 4 Mi × 8 33,557,944 3,448 3,448
Node 26 Mouse 4 Mi × 8 33,557,072 1,312 1,312
Electron 43 / Node 24 Kitty 64 Ki × 32 2,111,864 12,244 5,192
Electron 43 / Node 24 Mouse 64 Ki × 32 2,103,444 14,432 8,004
Electron 43 / Node 24 Kitty 4 Mi × 8 33,556,316 1,884 2,604
Electron 43 / Node 24 Mouse 4 Mi × 8 33,556,172 776 752

堆读数包含所有者开销,并在强制 GC 之后进行。测试框架在基线和修正用例中以相同方式清除 V8 的最近一次成功正则表达式输入,以隔离每个所有者的存储。该独立的进程级正则表达式引用可能使最近一次输入保持存活,直到下一次成功匹配;此变更不会消除它。在 Buffer 缺失时,Bufferless 选择会被记忆化,然后在测量前恢复 Buffer;它会执行实际的渲染器回退,而无需运行浏览器渲染器。

两个永久 kitty 堆回归在修正前分别以 33,560,832 和 33,575,040 保留字节失败,限制为 2-MiB。它们还验证保留的前缀能正确完成,并且重放/弹出/快照状态保持有效。现有解析器和复制器测试提供更广泛的协议对照。两个 mouse 回归同样在修正前以 33,559,240 和 33,573,200 字节失败,之后在两种 CSI 编码下均通过。五套件运行通过 98 个测试,包括实际无头模拟器模式快照;Node 和渲染器 TypeScript 检查通过。

调用方与生命周期

  • Kitty 渲染器窗格在 connect-pane-pty.ts:160 中为每个窗格创建或复用一个跟踪器。write-pty-output-to-xterm.ts:23 提供应用输出;apply-reattach-payload.ts 和 hidden-output-seq-and-skip.ts 提供重放。新派生和退出会重置它,terminal-pane-pane-closed.ts:69 会删除映射条目。仪表板预览为每个 effect 拥有另一个跟踪器(AgentTerminalPreview.tsx:116);清理会移除其监听器并释放其终端。
  • 主进程的 orca-runtime-capture-provider-terminal-buffer.ts:23 在 provider 快照获取期间注册临时实时扫描器,并在 finally 中移除它们。它仅在观察到备用屏幕转换后创建持久跟踪器(:48–57)。orca-runtime-on-pty-data.ts:30 在后续输出处理之前向这些跟踪器提供数据。退出、浮动 PTY 存活清理和 provider 代重置会删除持久条目。
  • 尽管其旧类注释如此,守护进程不会直接实例化 kitty 跟踪器:其 kitty 标志来自 xterm。没有移动端 bundle 导入此类。移动端可以执行主侧快照获取;SSH 输出可以通过现有 provider 路由到达主进程和渲染器跟踪器。
  • Mouse 镜像由 HeadlessEmulator(headless-emulator.ts:59)拥有。异步写入在 xterm 解析数据后扫描(:190);同步实时和冷恢复写入在 :224 扫描。守护进程会话和主进程的无头投影都使用该模拟器。因此它也覆盖为移动客户端提供服务的本地/远程主机模拟器。模拟器释放会停止未来写入;最终所有者释放会移除镜像。完成/替换其尾部也会释放旧后备字符串。所有权或关闭规则没有变化。

范围与限制

这是按所有者计的最后输入成本。在固定解析器集合和有界输入块的情况下,它不会无限增长,并且后续输出通常会补全或替换尾部。同一输入的多个读取器可以共享其底层存储;不要将其测量值相加作为独立进程内存。

交付给 main 的普通守护进程批量帧最多为 64 Ki 字符(daemon-stream-data-batcher.ts:35),普通中继输出切片为 16 Ki 字符(relay/pty-handler.ts:343)。守护进程内部的鼠标扫描发生在出站流分帧之前。64-Ki 用例在正常的 main 输入边界下演示了该问题;4-Mi 用例放大的是机制,并非声称普通原生读取或守护进程帧具有该大小。回放输入和转换后的流遵循其自身现有的限制。本证明未使用网络、应用渲染器、操作系统 PTY 或事件堆。

这在不改变已发布终端内容的情况下,减少了本地和 SSH 路径中保留的输出。它本身既未确立 #19831/#19768 中的触发条件,也无法解释所报告的持续增长速率或多 GB 事件。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 orca48/orca