Files
pebrel/scripts/windows-memory-stress.md
kuddev 96d4cf0179 fix(windows): reclaim terminal resources and retain font owners
Persist the pending ConPTY lifetime, process snapshot and native font ownership fixes. Keep lifecycle regression tests and the Windows memory stress tool together with the ownership contracts.
2026-09-15 09:44:00 +08:00

112 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Windows 产品内存压力测试
`windows_memory_stress.py` 启动真实 GPUI 产品,通过真实 PTY 写入持续输出,
再反复创建和关闭标签。它检查输出结束后内存、线程和句柄能否保持稳定。
测试使用独立配置、工作目录和临时目录;每次输出目录必须是新目录。
需要 Windows 原生 Python 3.11+,不需要第三方 Python 包。先构建真实产品:
```powershell
cargo build --release --locked -p nebula --bin pebrel --features gpui-shell
```
确认 `pebrel.exe` 同目录存在产品附带的 `conpty.dll` 和 `OpenConsole.exe`。
后面的命令在仓库根目录运行;构建及测试输出都应放在当前环境允许的路径内。
## 短回归与长测
以下预算用于本机 Windows 11、144 DPI、1280 × 900 外窗、默认 10000 行历史、
Maple Mono Normal NF CN 16 px 的回归条件。首次校准可不传预算,仅观察数据;
未配置预算的报告会明确标为观察运行。预算一旦选定,应在前后对照中保持一致,
超标时先定位分配和资源生命周期。
```powershell
python scripts/windows_memory_stress.py `
--app target/release/pebrel.exe `
--output tmp/memory-short-01 `
--cycles 8 --tabs 4 --payload-mib 11 `
--max-private-commit-mib 256 --max-growth-mib 16 `
--max-handle-growth 8 --max-thread-growth 4
python scripts/windows_memory_stress.py `
--app target/release/pebrel.exe `
--output tmp/memory-long-01 `
--cycles 32 --tabs 4 --payload-mib 64 `
--max-private-commit-mib 256 --max-growth-mib 16 `
--max-handle-growth 8 --max-thread-growth 4
```
长测包含约 2 GiB 混合 ASCII、CJK、Nerd Font 和 ANSI 彩色输出,以及
128 次标签关闭。每一轮输出后还写入 240 次交替屏幕重绘。
语料按完整块向上取整,报告记录实际字节数。它不是固定帧率动画或显示 FPS 测试。
程序会固定窗口大小,并从终端内读取真实行列数。比较两个程序时必须检查报告中的
行列数相同,仅设置相同像素尺寸并不足够。测试期间应保留正常显示的窗口,避免
输入、切换配置或调整窗口尺寸。将编译和其他高负载工作与正式测量分开。
## 测量口径与通过条件
| 数据 | 含义 |
| --- | --- |
| `private_commit_bytes` | Pebrel 自己的私有提交量,是本脚本内存预算的依据 |
| `private_working_set_bytes` | Pebrel 当前驻留在 RAM 中的私有页面 |
| `working_set_bytes` | 总工作集,包含驻留的共享页面,不能再与私有工作集相加 |
| `cpu_seconds` | Pebrel 累积的用户态和内核态 CPU 时间 |
| `handles` / `threads` | Pebrel 的句柄和线程数;线程在阶段检查点读取 |
每 100 ms 采样一次。完成输出并等待稳定、或关闭新增标签并恢复单标签后,
用该阶段最后五个内存样本的中位数比较。第一轮负责预热,后续每一轮都与它比较,
取最大增长量;不能让最后一次回收掩盖前面某一轮的残留增长。
脚本同时检查全部采样中的私有提交峰值、预热后的提交增长、句柄增长和线程增长。
输出必须在实际终端里出现完成标记;所有阶段必须齐全。测试窗口必须恢复到一个
标签并保持原几何状态,进程最终正常退出。任一预算超标或阶段失败均返回非零退出码。
关闭确认失败时保留现场进程树,不自动确认关闭来绕过产品的工作保护。
清理先对本次创建的窗口发送关闭请求。若失败,仅终止脚本保有句柄的那个测试进程,
并把该次运行记为失败。不会按进程名称批量结束实例。脚本没有设置进程内存上限、
调用工作集裁剪或生成完整内存转储。
## 证据文件
- `instance.json`:PID、进程创建时间、EXE SHA256、脚本 SHA256、Windows/Python
版本和逻辑处理器数;可通过 `--source-manifest <文件>` 同时记录源码清单的路径和 SHA256。
- `report.json`:实际窗口/网格、每轮输出量、提交峰值、残留增长及预算结果。
- `samples.json` / `checkpoints.json`:原始计数,即使测试中断也保存。
检查点还记录系统总物理内存、可用物理内存和内存负载,帮助识别页面驻留差异。
- `failure.json` / `failure-context.json`:失败原因、所处阶段及可读到的测试实例进程树。
- `cleanup.json`:是否正常退出,是否发生强制清理。
- `work/*-<轮次>.json`:终端内写入程序记录的实际输出量、行列数和写入耗时。
`drain_seconds` 包含 PTY 背压等待,不代表输入延迟或屏幕真正呈现的帧率。
100 ms 采样可能错过更短的峰值。计数不含 shell、输出程序或 WSL 虚拟机;
如需比较整个终端产品的进程组,应另行使用一致的进程归属和计数口径。
工作集还受页面驻留、其他应用和驱动影响,不能凭它下降就宣称减少了分配。
上述测试预算也不是应用在任意硬件、任意标签数下的运行时硬上限。
## 相关回归与实现依据
```powershell
python -m unittest scripts.tests.test_windows_memory_stress
cargo test --locked -p nebula_terminal --test windows_pty_lifecycle -- --nocapture
cargo test --locked -p nebula --bin pebrel --features gpui-shell process_tree:: -- --test-threads=1
```
本地终端输出沿用现有有界 PTY 缓冲和有限历史网格。此次回收修复关闭了创建
ConPTY 时遗留的调用方管道副本,让 EOF 能结束读取/排空线程;shell、主线程、
属性表和旁加载 DLL 使用各自的所有权释放。等待回调完成或取消后才释放其上下文,
避免把内存回收变成回调访问已释放数据。
字体修复由精确固定的 GPUI 分支提供:DirectWrite 持有字体数据的 COM 所有者,
静态字体直接使用程序映像中的字节,导入字体保留原 Vec,直到最后一个原生消费者
释放。原生内容、指针、引用寿命测试与真实窗口字体比对属于独立验收项。
Windows 会保留已经退出的父进程 PID;号码复用后,仅按 PID 连边会把无关进程
误接到新标签上。平台适配层现在一次读取进程创建时间,公共进程树规则断开
“父进程比子进程还年轻”的关系。缺失创建时间时保留关系,真实忙碌子进程仍受保护。
这使用已存在的 Windows 绑定,无额外后台轮询或持久化格式。
生命周期回归使用产品附带的 OpenConsole。Windows 11 build 22631 的系统内置
ConPTY 在独立最小探针中仍出现每次创建/关闭保留一个宿主句柄的问题;本次修改
没有修复该系统实现,也没有放宽产品回归预算去吸收它。