跳转至

插件工作进程输出保留

工作进程输出解析器将一行截断为最多 8,192 个代码单元,但保留的切片可能让更大的已解码输入块继续存活。本构件使用真实解析器和真实 PluginLogBuffer 复现两条所有权路径:

  1. 未完成的行保留在流监听器的缓冲区中。
  2. 已完成的短行或截断行保留在服务的 200 条日志环形缓冲区中。

修复方案使用现有的 ownRetainedString 复制器,用于跨回调保留的不完整段,以及传递给日志接收器的有界字符串。行内容、截断、回调调用、环形缓冲区容量和工作进程生命周期均未改变。短于 13 个代码单元的字符串继续使用该辅助函数现有的快速路径。

生产环境可达性与生命周期

  • src/main/plugins/plugin-host-process.ts 在第 101–102 行将解析器安装到子进程 stdout 和 stderr 上,解析器内部进行 UTF-8 解码。生产接收器经 plugin-worker-manager.ts:148 和 plugin-service.ts:94 传递到 plugin-log-buffer.ts:14,后者存储原始字符串而不进行复制。
  • 解析器每个流最多保留一个未完成行。默认的五个活动工作进程允许存在十个存活的 stdout/stderr 缓冲区。工作进程槽位在启动前获取。空闲工作进程每五分钟回收一次,每分钟检查一次;流结束时清除解析器缓冲。
  • 日志环形缓冲区属于长期存在的 PluginService,而非工作进程。工作进程退出和流结束会保留每个插件最后 200 条条目。环形缓冲区逐出会释放这些条目。多个行可以共享同一个父级;后备分配必须只计数一次。
  • 这是主进程插件路径。插件系统设置控制激活(src/main/startup/main-process-plugins.ts:59–62)。这不是终端守护进程或渲染器保留路径。插件 orca.log IPC 消息是一个独立的生产者。
  • PluginService.getLogs 及其 IPC 处理程序暴露现有环形缓冲区。读取或序列化拼接后的字符串可能会将其展平并缩短其父级保留时间,但不会移除服务的环形缓冲区条目。

复现

在仓库根目录并已安装项目依赖的情况下:

ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/plugin-worker-output-retention/reproduce.cjs

对于 Electron,使用 ELECTRON_RUN_AS_NODE=1、ORCA_BACKGROUND_LAUNCH=1 和相同参数运行已安装的 Electron 可执行文件。这会使用 Node 模式,且不会打开应用窗口。例如,在 macOS 上:

ORCA_BACKGROUND_LAUNCH=1 ELECTRON_RUN_AS_NODE=1 node_modules/electron/dist/Electron.app/Contents/MacOS/Electron --expose-gc --max-old-space-size=192 docs/audits/plugin-worker-output-retention/reproduce.cjs

运行器会在自身旁边写入 node-results.json 或 electron-results.json。传入 --output <path> 可保留捕获的报告。它使用惰性 PassThrough 流,不涉及操作系统子进程或网络,堆限制为 192 MiB,超时时间为 30 秒。

sources.cjs 在内存中反转 fix.patch,并检查基线/修复后解析器的精确哈希。它还检查八个依赖/调用方哈希,并记录实际求值的源码和打包产物哈希。不会覆盖任何源文件。一个合成的 CRLF 读取控制检查所有十个源/补丁读取。

source-versions.json 记录了解析器、接收器、调用方和辅助函数在 main 检查点 291b4ddd6f1c1af480169885e0fda7f9c78ff053、main f78483ec29891ab11f49bb25e6cd628837b1242e 以及 #20960 主题 np-oom-scan-retained-text-slices 的 0d2efbc0d3b4f902b9bc02f5451c6ff4291405cf 处具有相同的哈希。解析器、接收器和调用方模块还与 v1.4.198(e0826956fcfc532f5a1e55b5e081f2e57e553c43)匹配,该版本缺少较新的 own-retained-string.ts 包装器。基线打包产物仅使用未更改的解析器和环形缓冲区;修复变体使用记录的发布辅助函数。这些是带有当前构建依赖的源码控制,而非历史应用二进制。

控制项与结果

Node 26.6 和 Electron 43.7 / Node 24.21 均通过 24 个用例:基线、诊断性仅尾部复制、修复后 Buffer 复制,以及修复后代码单元复制回退,每个用例包含两种输入大小和三种所有权情形。回退是共享辅助函数的兼容性控制;生产主分支通常使用 Buffer。

保留所有者 基线堆增量 修复后堆增量
十个未完成尾部,每个 64 KiB 输入 0.72–0.73 MB 9–24 KB
八个未完成尾部,每个 4 MiB 输入 33.56–33.57 MB 6–11 KB
来自 205 × 64 KiB 输入的 200 条短日志行 13.14–13.16 MB 27–45 KB
来自 205 × 64 KiB 输入的 200 条截断日志行 13.14–13.15 MB 3.29–3.31 MB
八个短日志行,每个 4 MiB 输入 33.56 MB 约 1 KB
八个截断日志行,每个 4 MiB 输入 33.56 MB 128–132 KB

堆增量包含 GC 噪声。截断字符串会合理地保留 8,192 个代码单元,包括非 ASCII 截断后缀。仅尾部复制修复了未完成缓冲区,但两条日志环形缓冲区路径仍然存在。流结束会清除无操作接收器的尾部,而实际环形缓冲区仍然存活;替换全部 200 条条目会释放原始父级。

64 KiB 是常规规模的 stdio 输入控制。4 MiB 输入是放大的压力测试,并非声称普通操作系统管道读取就是如此。PassThrough 会完整交付所选块;真实子进程管道块大小取决于运行时和操作系统。保留量受所有者数量、环形缓冲区容量和后备输入大小限制;这不是无界行队列。

行为比较涵盖空行和拆分行、null/空流、CRLF、结束刷新、溢出后的丢弃/恢复、日志级别、精确环形缓冲区内容、NUL、孤立代理项、emoji 以及代码单元限制。值比较与堆控制分开运行,因为比较拼接后的字符串可能会将其展平并改变保留行为。

验证

ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/plugins/plugin-worker-output-retention.test.ts src/main/plugins/plugin-worker-output-buffer.test.ts src/main/plugins/plugin-host-process.test.ts src/shared/own-retained-string.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/plugin-worker-output-retention/before.config.mjs src/main/plugins/plugin-worker-output-retention.test.ts src/main/plugins/plugin-worker-output-buffer.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm tc:node

修复后的源码通过 20 个测试。基线叠加层有意使全部三个新的堆回归失败:未完成尾部约 33.6 MB,每个环形用例 13.2 MB,而上限分别为 2 MiB 和 5 MiB;其原有行为测试通过。Node 类型检查通过。全部五个变更质量扫描配置在全部五个产品/测试/产物代码文件上均通过,使用 --no-ignore --deny-warnings,包括普通和类型感知 lint 规则。

这证明了一个可达的代码机制及其修复。它并未确认受影响主机上的插件使用、输出频率、应用总 RSS,或归因于 #19831 或其他事件。

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