Files
StellarX-kaifa/开发记录/Fix/Fix-BUG-20261004-0001-原生窗口销毁后的宿主循环退出修复.md

7.1 KiB
Raw Permalink Blame History

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.h
    • src/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 不能被无条件当作成功。

验证结果

  • 验证步骤:
  1. 默认探测关闭构建 StellarX.Core、StellarX.UiFixture。
  2. 执行 Debug x64 单元测试。
  3. 运行原有六个 UI 场景,并分别对 native-close 普通循环和 modal-close 真实模态循环发送 WM_SYSCOMMAND/SC_CLOSE;input-probe 仅保留给人工 IME 探测。
  4. 断言两种关闭场景都在 5 秒内自行退出,并分别生成 close-result.json。
  5. 以 /p:StellarXInputProbe=true 重建,运行探测开启的 UI 关闭 smoke,逐个解析 JSONL 并检查 sequence、丢失和文字原值泄露。
  6. 对真实微软拼音日志运行 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-20980
    • TestResults/InputProbe/close-smoke-20261004-203149-536
    • TestResults/InputProbe/manual-20261004-194521-908-MicrosoftPinyin
    • TestResults/InputProbe/manual-20261004-204556-353-Sogou
    • TestResults/InputProbe/manual-20261004-223833-893-Sogou
    • TestResults/InputProbe/request-change-ui-regression-final-20261004
    • TestResults/InputProbe/manual-20261004-231414-758-Sogou
    • TestResults/InputProbe/automated-precommit2
    • TestResults/easyx-thread-probe/20261005-005952-040-7276.json

落地信息

  • Commit: c0eca1d(生产修复);c218c25(阶段 0A 诊断与复现)
  • PR:无
  • 发布版本:未发布
  • 备注: 未删除任何探测结果;微软拼音和搜狗真实输入法人工测试均已完成。