7.1 KiB
7.1 KiB
Fix-BUG-20261004-0001
适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
关联信息
- Fix ID: Fix-BUG-20261004-0001
- 关联 BUG ID: BUG-20261004-0001
- 修复目标: 原生 EasyX HWND 销毁后,让普通和模态宿主事件循环安全退出
- 状态:已完成
- 负责人: Codex
- 分支 / 版本:
master,生产修复基线c0eca1d
根因分析
- 根因: EasyX 原生 WndProc 与 StellarX 宿主
peekmessage循环分属不同线程;原生窗口线程收到并处理WM_CLOSE/WM_DESTROY/WM_NCDESTROY时,宿主队列不保证出现WM_CLOSE。旧实现却只依赖宿主消息退出,因此 HWND 已销毁后循环仍然存活。 - 触发条件: 用户点击标题栏关闭、发送
SC_CLOSE,或在模态循环期间销毁宿主原生窗口。 - 为什么之前没发现: 旧设计隐含假定 WndProc 与宿主循环同线程且关闭消息会进入 EasyX
EX_WINDOW;阶段 0A 的双后端时序日志和独立线程探针才将该假定证伪。 - 关键证据: 微软拼音真实日志中 WndProc 链完整观察到
WM_CLOSE -> WM_DESTROY -> WM_NCDESTROY,宿主线程没有取得WM_CLOSE,窗口消失后进程仍挂起。
修复方案
- 修复思路: 由 WndProc 在线程安全边界发布原生窗口生命周期,宿主普通循环和模态循环在每轮开始及消息批次结束后读取该状态并退出。
- 关键改动:
Window增加std::atomic<bool> nativeWindowAlive,WndProc 生产者使用 release 发布,宿主循环使用 acquire 读取。draw()在取得有效 HWND 后初始化生命周期状态;子类化失败时退回IsWindow()兜底。WndProcThunk()只在旧窗口过程与探测 leave 都结束后,且作为对self的最后一次访问,于WM_NCDESTROY发布false。Window::runEventLoop()在每轮开始和批次结束后检查状态,窗口死亡时在安全点 flush 探测记录并返回。Dialog::Show()在进入模态、每轮开始和批次结束后检查状态,并检查两处GetClientRect返回值;窗口死亡后不再执行尺寸轮询、绘制或访问失效 HWND。- 析构时只对仍存活的窗口恢复旧 WndProc,仍调用
closegraph()完成 EasyX 清理。 - 人工探测脚本先分析日志再判断退出码;仅在日志序列严格递增、无文字原值泄露且观察到原生关闭时接受
0xC000013A,不泛化忽略异常退出。
- 涉及文件 / 类 / 函数:
include/StellarX/Window.hsrc/StellarX/Window.cpp:Window::draw()、Window::runEventLoop()、Window::WndProcThunk()、析构函数src/StellarX/Dialog.cpp:Dialog::Show()tests/UiFixture.cpp:native-close与modal-close场景scripts/run-ui-tests.ps1:普通与模态原生关闭回归scripts/run-input-probe-manual.ps1:退出码与日志证据联合判定
- 影响的 API / 行为: 无公共 API 变化;窗口关闭后的宿主进程现在会自行结束。
- 关键约束 / 不变量:
- 不得在转发
WM_NCDESTROY前发布死亡状态,否则宿主可能提前析构仍在执行的WndProcThunk,形成 use-after-free。 - 发布
false后 WndProc 不得再访问self。 - 模态循环发现窗口死亡后不得继续
GetClientRect、绘制或处理控件。
- 不得在转发
- 回滚点 / 开关: 回退
nativeWindowAlive、普通/模态循环检查及对应关闭回归;探测脚本调整可独立回退。
影响评估
- 影响范围: Window 原生生命周期、普通事件循环、Dialog 模态循环、输入探测收尾。
- 兼容性影响:无公共 API 或控件事件语义变化。
- 行为变化:有;原生窗口销毁后事件循环不再等待一个可能永远不会进入宿主队列的
WM_CLOSE。 - 性能影响:每轮增加少量 atomic acquire 读取,可忽略。
- 回归风险:
- 生命周期发布过早会使
Window在 WndProc 尚未返回时被析构。 - 子类化失败路径若只依赖 atomic,可能无法观察真实 HWND 状态,因此保留
IsWindow()兜底。 - 模态循环退出顺序错误可能继续绘制失效 HWND 或遗漏
dialogOpen清理。 EX_SHOWCONSOLE托管运行的0xC000013A不能被无条件当作成功。
- 生命周期发布过早会使
验证结果
- 验证步骤:
- 默认探测关闭构建
StellarX.Core、StellarX.UiFixture。 - 执行 Debug x64 单元测试。
- 运行原有六个 UI 场景,并分别对
native-close普通循环和modal-close真实模态循环发送WM_SYSCOMMAND/SC_CLOSE;input-probe仅保留给人工 IME 探测。 - 断言两种关闭场景都在 5 秒内自行退出,并分别生成
close-result.json。 - 以
/p:StellarXInputProbe=true重建,运行探测开启的 UI 关闭 smoke,逐个解析 JSONL 并检查 sequence、丢失和文字原值泄露。 - 对真实微软拼音日志运行
analyze-input-probe.ps1,检查关闭证据与隐私字段。
- 验证结果:
- Debug x64 默认构建通过;VSTest 15/15 通过。
- 2026-10-04 UI 回归通过 1 轮;普通和模态关闭均在 5 秒内自行退出,
close-result.json均为nativeCloseExited=true。 - 探测开启的 Core/UiFixture 完整重建通过。
- 2026-10-05 探测开启的最终自动关闭 smoke 产生 8 份可解析 JSONL:全部 sequence 严格递增、
closeObserved=true、无文字原始wParam泄露、无records_lost;自动原生关闭均未伪造session_end,人工正常关闭会话则已验证可写出window_detach/session_end。 - 真实微软拼音日志可解析、sequence 严格递增、
closeObserved=true、文字原值泄露为 0;存在 5 条已显式记录的wndproc_lock_contention丢失。 - 搜狗真实提交日志正常写出
WM_CLOSE/WM_DESTROY/WM_NCDESTROY、window_detach和session_end;第二次短输入布局会话同样正常收口,证明修复后的人工脚本能够在关闭后稳定进入摘要阶段。 - 输入布局 request 补观测后重新执行 1 轮完整 UI 回归并通过;其中普通与模态原生关闭断言仍在时限内退出,说明新增被动消息观察没有回归关闭行为。
- request 补观测后的专用 Win+Space 会话也正常写出
WM_CLOSE/WM_DESTROY/WM_NCDESTROY、window_detach与session_end。
- 回归检查: 正常事件循环、真实模态循环、探测关闭、探测开启、原生标题栏关闭。
- 验证证据:
TestResults/ui/20261004-203032-495-20980TestResults/InputProbe/close-smoke-20261004-203149-536TestResults/InputProbe/manual-20261004-194521-908-MicrosoftPinyinTestResults/InputProbe/manual-20261004-204556-353-SogouTestResults/InputProbe/manual-20261004-223833-893-SogouTestResults/InputProbe/request-change-ui-regression-final-20261004TestResults/InputProbe/manual-20261004-231414-758-SogouTestResults/InputProbe/automated-precommit2TestResults/easyx-thread-probe/20261005-005952-040-7276.json
落地信息
- Commit:
c0eca1d(生产修复);c218c25(阶段 0A 诊断与复现) - PR:无
- 发布版本:未发布
- 备注: 未删除任何探测结果;微软拼音和搜狗真实输入法人工测试均已完成。