fix(windows-ime): stop explorer.exe AppHang from IME pipe server shutdown#764
Merged
Merged
Conversation
…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>
Contributor
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
H-Chris233
approved these changes
Jul 5, 2026
H-Chris233
left a comment
Collaborator
There was a problem hiding this comment.
代码方向和实现思路看起来没问题:将 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、语音插入回归)。通过后我认为可以合并。
|
win11试用了最新的beta版本,资源管理器再次出现了自动重启的情况。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 线程:ConnectNamedPipe(pipe, nullptr)上同步、无限阻塞等待客户端。Deactivate() → Stop() → thread.join(),必须等 worker 退出。CancelSynchronousIo(worker 尚未进入阻塞调用时是 no-op)和自连接WakePipe()(与Run()创建/关闭管道的窗口期竞态,命中ERROR_FILE_NOT_FOUND/ERROR_PIPE_BUSY即静默失效)。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
io_event_复用、ConnectNamedPipe语义(ERROR_PIPE_CONNECTED/ERROR_IO_PENDING/同步完成)、泄漏 / 双重 Stop / Start-after-Stop / Stop-without-Start、死锁、排空路径——无 CRITICAL/HIGH/MEDIUM 正确性问题。ci.yml四平台门禁只做 Rust/前端,OpenLessIme.dll仅在release-tauri.yml(MSBuild / C++17 //MT)发版时编译。因此本 PR 的 CI 绿 不代表 C++ 已编译验证。scripts/windows-ime-build.ps1 -Configuration Release -Platform x64编译通过;explorer.exeAppHangB1;备注 / Notes
Deactivate那一刻恰有一次听写正通过SubmitTextFromPipe的SendMessageTimeoutW(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"]File Walkthrough
ipc_client.cpp
Convert pipe server to overlapped I/O with stop eventopenless-all/app/windows-ime/src/ipc_client.cpp
cancellation.
ipc_client.h
Update header for overlapped I/O changesopenless-all/app/windows-ime/src/ipc_client.h