Skip to content

fix(windows-ime): stop explorer.exe AppHang from IME pipe server shutdown#764

Merged
H-Chris233 merged 1 commit into
betafrom
fix/issue-707-windows-ime-explorer-hang
Jul 6, 2026
Merged

fix(windows-ime): stop explorer.exe AppHang from IME pipe server shutdown#764
H-Chris233 merged 1 commit into
betafrom
fix/issue-707-windows-ime-explorer-hang

Conversation

@appergb

@appergb appergb commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

User description

问题 / Problem

Windows 11 上开启 OpenLess 后,资源管理器 / 任务栏偶发(部分机器必现)无响应,explorer.exe 记录 AppHangB1,随后自动重启。见 #707#665

OpenLessIme.dll系统级启用的 TSF 键盘输入法(TIP),Windows CTF 会把它注入每一个有文本输入的进程(含 explorer.exe、任务栏),并运行在各宿主进程的 UI 线程上。#665 的 WER 日志证实挂起时 OpenLessIme.dll 就加载在 explorer.exe 里,且禁用该 TSF 配置后 12+ 小时不复现、重新启用又复现。

根因 / Root cause

windows-ime/src/ipc_client.cpp 的管道服务器关闭路径会挂死宿主 UI 线程:

  • 后台 worker 线程在 ConnectNamedPipe(pipe, nullptr)同步、无限阻塞等待客户端。
  • TSF 在切输入法 / 焦点 / 线程销毁时于宿主 UI 线程调用 Deactivate() → Stop() → thread.join(),必须等 worker 退出。
  • 唤醒那个阻塞只靠两个有竞态的机制:CancelSynchronousIo(worker 尚未进入阻塞调用时是 no-op)和自连接 WakePipe()(与 Run() 创建/关闭管道的窗口期竞态,命中 ERROR_FILE_NOT_FOUND/ERROR_PIPE_BUSY 即静默失效)。
  • 两个唤醒都输掉竞态时,worker 重新进入 ConnectNamedPipe 永久阻塞,join() 就把宿主 UI 线程永久冻结explorer.exe AppHangB1。竞态性质解释了「偶发 vs 必现」的差异。

修复 / Fix

把每一次管道等待都改成可确定性取消

  • 管道用 FILE_FLAG_OVERLAPPED 打开;新增两个手动复位事件 stop_event_ / io_event_(在 Start() 里、spawn 线程前创建,Stop() join 之后关闭)。
  • 连接 / 读 / 写统一走新的 WaitForClient / RunOverlapped,都用 WaitForMultipleObjects({io_event_, stop_event_}) 等待;停止或超时路径 CancelIoEx + GetOverlappedResult(bWait=TRUE) 排空栈上 OVERLAPPED 与缓冲区后再返回。
  • Stop() 现在只需 SetEvent(stop_event_)join()——立即返回,永不阻塞宿主 UI 线程,也不泄漏 worker 线程
  • 删除竞态的 WakePipe / CancelSynchronousIo / pipe_handle_ / pipe_mutex_

管道名、JSON 线协议、Rust 客户端(windows_ime_ipc.rs)完全不变——只改宿主进程内那段等待逻辑。

验证 / Verification

  • ✅ 独立 C++ 评审(逐行):OVERLAPPED 生命周期、io_event_ 复用、ConnectNamedPipe 语义(ERROR_PIPE_CONNECTED/ERROR_IO_PENDING/同步完成)、泄漏 / 双重 Stop / Start-after-Stop / Stop-without-Start、死锁、排空路径——无 CRITICAL/HIGH/MEDIUM 正确性问题
  • ⚠️ CI 不编译该 DLLci.yml 四平台门禁只做 Rust/前端,OpenLessIme.dll 仅在 release-tauri.yml(MSBuild / C++17 / /MT)发版时编译。因此本 PR 的 CI 绿 不代表 C++ 已编译验证。
  • 待人工验证(Windows 实机)
    1. Windows 上 scripts/windows-ime-build.ps1 -Configuration Release -Platform x64 编译通过;
    2. 装 IME、日常使用,切输入法 / 焦点期间不再出现 explorer.exe AppHangB1
    3. 语音听写插入(TSF 提交)功能回归正常。

备注 / Notes

  • 独立评审另指出一处有界、非本次范围的既有细节:若 Deactivate 那一刻恰有一次听写正通过 SubmitTextFromPipeSendMessageTimeoutW(2s 上限)提交,Stop()join() 至多被拖慢约 2 秒——远低于 AppHang 的 ~5s 阈值,且本 PR 之前就存在,未改动。
  • openless-all/app-opencode/windows-ime/ 有一份未打包的同源副本,存在同样的 bug,但不参与发版构建,本 PR 未改动。
  • 🔴 未触碰更新器签名公钥,无版本号变更。

Fixes #665
Fixes #707

🤖 Generated with Claude Code


PR Type

Bug fix


Description

  • Converted pipe server to overlapped I/O with events.

  • Made Stop() immediately cancel waits via stop_event.

  • Removed racy WakePipe and CancelSynchronousIo.

  • Prevented UI thread freeze on shutdown.


Diagram Walkthrough

flowchart LR
    Old["Sync ConnectNamedPipe"] -->|"blocked indefinitely"| Hang["explorer.exe AppHang"]
    New["Overlapped ConnectNamedPipe + stop_event"] -->|"deterministic cancel"| Exit["thread.join() returns promptly"]
Loading

File Walkthrough

Relevant files
Bug fix
ipc_client.cpp
Convert pipe server to overlapped I/O with stop event       

openless-all/app/windows-ime/src/ipc_client.cpp

  • Added event handles (stop_event_, io_event_) for deterministic
    cancellation.
  • Replaced synchronous ConnectNamedPipe with overlapped WaitForClient.
  • Replaced ReadFile/WriteFile with RunOverlapped for cancelable I/O.
  • Removed racy WakePipe and CancelSynchronousIo calls.
  • Updated Start/Stop to manage event lifecycle.
+109/-40
ipc_client.h
Update header for overlapped I/O changes                                 

openless-all/app/windows-ime/src/ipc_client.h

  • Removed pipe_mutex_, pipe_handle_ fields.
  • Added stop_event_, io_event_ HANDLE fields.
  • Declared WaitForClient and RunOverlapped methods.
  • Removed WakePipe declaration.
+12/-4   

…down

OpenLessIme.dll is a system-wide TSF keyboard TIP, so CTF loads it into
every process that has text input (explorer.exe, the taskbar, ...) and
runs it on that process's UI thread. The per-process pipe server blocked
on a synchronous ConnectNamedPipe, and Deactivate() -> Stop() joined that
worker from the host UI thread, relying on a racy CancelSynchronousIo +
self-connect WakePipe() to unblock it. When both lost the race the worker
re-entered ConnectNamedPipe forever and join() froze the host UI thread
indefinitely -> explorer.exe AppHangB1 (taskbar unresponsive, Explorer
restarts). WER reports for the reported hangs show OpenLessIme.dll loaded
in explorer.exe, and disabling the TSF profile stops the hang.

Make every pipe wait deterministically cancelable: open the pipe with
FILE_FLAG_OVERLAPPED and wait on {io_event, stop_event} via
WaitForMultipleObjects. Stop() now just signals stop_event and joins, so
it returns at once, never blocks the host UI thread, and leaks no worker
thread. Connect/read/write go through WaitForClient / RunOverlapped, which
CancelIoEx + drain the overlapped op before the stack OVERLAPPED / buffer
go out of scope. Removes the racy WakePipe / CancelSynchronousIo /
pipe_handle_ / pipe_mutex_.

Pipe name, JSON wire protocol and the Rust client (windows_ime_ipc.rs)
are unchanged.

Fixes #665
Fixes #707

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

707 - PR Code Verified

Compliant requirements:

  • Fix resource manager (explorer.exe) unresponsiveness caused by OpenLess IME TSF component.

Requires further human verification:

  • Verification on actual Windows 11 systems with the IME enabled to confirm the hang no longer occurs. The code change is logically sound, but the intermittent nature of the original bug means environmental testing is the only way to guarantee the fix.

665 - Fully compliant

Compliant requirements:

  • Intermittent explorer.exe/taskbar AppHang resolved by making the pipe server shutdown deterministic and cancelable.
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected

@H-Chris233 H-Chris233 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代码方向和实现思路看起来没问题:将 IME pipe server 改为 overlapped I/O + stop_event 后,能避免 Stop() 卡在同步 ConnectNamedPipe 上,修复 explorer.exe / taskbar AppHang 的核心路径。

合并前建议补充确认两点:Windows x64/Win32 native IME DLL 编译通过,以及 Windows 11 实机 smoke test 通过(切输入法、焦点切换、关闭 OpenLess、语音插入回归)。通过后我认为可以合并。

@H-Chris233 H-Chris233 merged commit 2e3c0f5 into beta Jul 6, 2026
5 checks passed
@uqsmile-debug

Copy link
Copy Markdown

win11试用了最新的beta版本,资源管理器再次出现了自动重启的情况。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants