降低按键延迟并修复服务生命周期通知#1867
Open
a810439322 wants to merge 9 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
该 PR 聚焦于 TSF 按键热路径卡顿 与 服务生命周期/托盘通知缺失 两个用户可感知问题:通过更明确的按键 IPC 结果语义、降低 IPC 连接/事务的同步阻塞、并将服务通知收口到 server 消息线程,来减少输入线程被 IPC/锁/重连拖慢的概率,同时补齐部署/退出/重启相关托盘提示。
Changes:
- 新增按键 IPC 命令与结果编码(processed/eaten),并改造 TSF 按键路径使用“带状态返回”的处理接口以避免把 IPC 失败误判为“未吃键”。
- Pipe/Client/Server 三端引入
TryConnect/TryTransact/try_lock等一次性/非阻塞路径,并增加输入位置与托盘/UI 刷新去重,降低热路径阻塞。 - 补齐维护模式结果、服务退出/重启语义与通知链路(NotifyService/WM_* 收口),同时更新安装/脚本参数与单元测试覆盖。
Reviewed changes
Copilot reviewed 38 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| WeaselTSF/WeaselTSF.h | TSF 侧新增重连/恢复与按键缓存成员、接口调整 |
| WeaselTSF/WeaselTSF.cpp | TSF 激活/重连/异步恢复路径与日志增强 |
| WeaselTSF/LanguageBar.cpp | 语言栏菜单、服务启动/退出命令与根目录发现逻辑更新 |
| WeaselTSF/KeyEventSink.cpp | TestKey/Key 处理缓存、防重复 keydown、非阻塞 IPC 与追踪 |
| WeaselTSF/Composition.cpp | 本地 abort 同步清 composing 与测试键缓存 |
| WeaselSetup/WeaselSetup.cpp | 安装/升级流程改为 /stop + /restart-manual 语义 |
| WeaselSetup/imesetup.cpp | 安装复制逻辑改为“安全替换/延迟替换”策略与注释更新 |
| WeaselServer/WeaselTrayIcon.h | 托盘气泡文案/图标映射与新通知入口声明 |
| WeaselServer/WeaselTrayIcon.cpp | 展示维护结果与服务通知气泡实现 |
| WeaselServer/WeaselService.cpp | SCM stop 语义与 shutdown reason 调整 |
| WeaselServer/WeaselServerApp.h | ServerApp 构造支持启动通知参数 |
| WeaselServer/WeaselServerApp.cpp | 托盘初始化、通知回调绑定与 quit 行为调整 |
| WeaselServer/WeaselServer.cpp | 服务命令行语义、manual-exit flag、重启等待与通知链路 |
| WeaselServer/resource.h | 新增部署/服务通知相关字符串资源 ID |
| WeaselIPCServer/WeaselServerImpl.h | ServerImpl 新 IPC 命令、服务通知消息与分发接口 |
| WeaselIPCServer/WeaselServerImpl.cpp | 外层锁策略、NotifyService 收口、shutdown reason/通知与 pipe 线程行为 |
| WeaselIPC/WeaselClientImpl.h | ClientImpl 引入互斥、TryConnect/Reconnect、输入位置缓存与新接口 |
| WeaselIPC/WeaselClientImpl.cpp | 非阻塞发送、重连语义、托盘同步命令与 session/cache reset |
| WeaselIPC/PipeChannel.cpp | 一次性连接 _EnsureOnce 与写入 flush 控制 |
| WeaselDeployer/Configurator.cpp | 维护模式/部署结果回传与 SyncUserData 失败恢复 |
| test/TestWeaselIPC/TestWeaselIPC.cpp | 新增 /unit 覆盖 IPC 结果编码、策略函数与多 helper |
| RimeWithWeasel/RimeWithWeasel.cpp | 按键吃键语义增强、UI/托盘 diff 去重与服务通知回调 |
| output/stop_service.bat | stop 脚本改用 /stop |
| output/start_service.bat | start 脚本改用 /restart-manual |
| output/install.nsi | 安装/卸载/自启命令行参数更新与错误码处理 |
| output/install.bat | Run key 与启动参数改为 /startup,清理遗留注释 |
| include/WeaselUtility.h | 新增 trace 开关与文件日志工具函数 |
| include/WeaselIPCData.h | 多个结构比较运算符 const-correctness 修正 |
| include/WeaselIPC.h | 新 IPC 命令、结果编码、通知/维护枚举与服务命令行工具函数 |
| include/RimeWithWeasel.h | UI diff、托盘签名、状态快照与通知回调接口声明 |
| include/resource.h | 公共资源 ID 对齐并新增通知字符串 ID |
| include/PipeChannel.h | TryConnect/TryTransact 与 flush 控制接口 |
| include/KeyEvent.h | TestKey 策略、缓存/重复 keydown guard、reset helper 等 |
| docs/weasel-change-review-reference.md | 新增本地改动 review 路线图与检查清单 |
| docs/key-event-latency-pr.md | 新增 PR 说明文档(背景/改动/风险/验证) |
| docs/key-event-latency-pr-review.md | 新增 review 记录/风险点与处理过程文档 |
| .gitignore | 忽略更多构建产物与部分 output/data 目录 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+69
to
70
| { RegisterApplicationRestart(weasel::ServiceRecoveryArgument(), 0); } | ||
| boost::thread{[this] { app.Run(); }}; |
Comment on lines
+317
to
+320
| boost::thread([this] { | ||
| Sleep(1200); | ||
| Stop(); | ||
| }).detach(); |
Comment on lines
+73
to
+76
| std::thread([this] { | ||
| std::this_thread::sleep_for(std::chrono::milliseconds(1200)); | ||
| m_server.Stop(); | ||
| }).detach(); |
Comment on lines
+59
to
+91
| inline void WeaselDebugLog(const std::wstring& component, | ||
| const std::wstring& message) { | ||
| try { | ||
| SYSTEMTIME st; | ||
| GetLocalTime(&st); | ||
| WCHAR prefix[256] = {0}; | ||
| swprintf_s(prefix, _countof(prefix), | ||
| L"%04d-%02d-%02d %02d:%02d:%02d.%03d pid=%lu tid=%lu [%s] ", | ||
| st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, | ||
| st.wMilliseconds, GetCurrentProcessId(), GetCurrentThreadId(), | ||
| component.c_str()); | ||
| std::wstring line = std::wstring(prefix) + message + L"\r\n"; | ||
| int bytes = WideCharToMultiByte(CP_UTF8, 0, line.c_str(), | ||
| static_cast<int>(line.size()), NULL, 0, | ||
| NULL, NULL); | ||
| if (bytes <= 0) | ||
| return; | ||
| std::string utf8(bytes, '\0'); | ||
| WideCharToMultiByte(CP_UTF8, 0, line.c_str(), | ||
| static_cast<int>(line.size()), utf8.data(), bytes, | ||
| NULL, NULL); | ||
|
|
||
| fs::path log_file = WeaselLogPath() / L"weasel-debug.log"; | ||
| HANDLE file = | ||
| CreateFileW(log_file.c_str(), FILE_APPEND_DATA, | ||
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | ||
| NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); | ||
| if (file == INVALID_HANDLE_VALUE) | ||
| return; | ||
| DWORD written = 0; | ||
| WriteFile(file, utf8.data(), static_cast<DWORD>(utf8.size()), &written, | ||
| NULL); | ||
| CloseHandle(file); |
Comment on lines
+171
to
+176
| BOOL _InitKeyEventSink(); | ||
| void _UninitKeyEventSink(); | ||
| bool _TestKeyEvent(WPARAM wParam, LPARAM lParam, BOOL* pfEaten); | ||
| bool _ProcessKeyEvent(WPARAM wParam, LPARAM lParam, BOOL* pfEaten); | ||
| bool _CanHandleKeyEvent(); | ||
| void _ResetKeyEventTestCacheIfNeeded(); |
Comment on lines
+56
to
+68
| bool WeaselTSF::_TestKeyEvent(WPARAM wParam, LPARAM lParam, BOOL* pfEaten) { | ||
| *pfEaten = FALSE; | ||
| if ((_isToOpenClose && !_IsKeyboardOpen()) || _IsKeyboardDisabled()) { | ||
| TRACE_KEY_EVENT(L"Test", wParam, lParam, | ||
| L"skip disabled_or_keyboard_closed=1"); | ||
| return false; | ||
| } | ||
| bool service_available = _CanHandleKeyEvent(); | ||
| if (!service_available) { | ||
| TRACE_KEY_EVENT(L"Test", wParam, lParam, | ||
| L"skip manual_exit_or_recovery_disabled=1"); | ||
| return false; | ||
| } |
Author
|
对了,我本地编译验证用的librime和plum都是最新仓库下载的,暂时用着没什么大问题,一些rime的陈年BUG也修复了,比如退出算法服务后点重启算法服务没反应,比如shift切换中英图标关闭后引发的不能切换,我在代码里直接关闭了这个图标,他的位置真的很影响打字,还有加了一系列的托盘提示,要不然用户真的不知道点了重新部署或者重启算法有没有生效 至于卡顿优化问题,我也是尝试修复,不一定修好,不过也用gpt进行了三四轮的review,自己体验还可以,你们不放心的话可以选有用的集成进去 |
Contributor
|
不必要的文件格式修改应该避免,不然很难review |
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.
perf: 降低按键延迟并修复服务状态通知
背景
这批改动针对两个用户可感知的问题:
这里不把 Shift、CapsLock 等主动切换行为当成根因,也不改变它们的既有语义。本轮没有保留 JetBrains/IDEA/Rider 专用分支,也没有引入 120ms/15ms 之类按键延迟 hack。
主要改动
1. 按键 IPC 语义更明确
WEASEL_IPC_PROCESS_KEY_EVENT_WITH_STATUS。handled=false,也视为输入法处理过,避免 Backspace 清掉最后一个编码字符时被宿主误处理。ProcessKeyEvent(key, bool* eaten),避免把 IPC 失败误判成“服务端处理了但没吃键”。WEASEL_IPC_PROCESS_KEY_EVENT也补了 session 守卫,和新入口语义保持一致。2. 降低按键热路径阻塞
PipeChannel增加一次性连接/请求路径:TryConnect()只尝试一次连接,不进入WaitNamedPipe循环。TryTransact()跳过热路径FlushFileBuffers。ClientImpl增加内部互斥保护session_id、pipe channel、输入位置缓存等共享状态。try_lock和TryTransact,恢复线程持锁时快速失败,不等待恢复锁。g_api_mutex,改为 try-lock;锁忙时立即返回未处理,让客户端把当前键交回宿主。Echo()改走非阻塞_TrySendMessage(WEASEL_IPC_ECHO),避免 echo 失败路径自己造成阻塞重连。3. 后台恢复不触碰 TSF apartment 对象
_Reconnect(update_tsf_status, wait_for_pipe)支持区分 UI 线程和后台恢复线程。_status、不调用_UpdateLanguageBar,避免跨线程访问 TSF compartment 或 language bar 对象。AddRef/Release改为InterlockedIncrement/InterlockedDecrement,恢复线程持有 TSF 对象引用时使用原子引用计数。4. 避免按键响应丢失和 Backspace 状态滞后
ClientImpl::GetResponseData()不再抢client_mutex;response buffer 是PipeChannel的 thread-local 数据,不应被后台恢复线程阻塞。_ProcessKeyEvent()在ProcessKeyEvent成功后立即解析一次 status-only 响应,提前刷新_status.composing。_AbortComposition()会同步把_status.composing置为 false,并清理 TestKey cache,避免 TSF 本地状态残留影响下一次按键预测。5. TestKey 单次处理缓存,重复 keydown 防护更保守
OnTestKeyDown/OnTestKeyUp,不保证后续一定按预期触发正式OnKeyDown/OnKeyUp。ActiveKeyDownGuard改为只判断是否应 suppress,不再在判断时自动记录。OnKeyDown只有在_ProcessKeyEvent()成功且pfEaten=TRUE后才记录 active key,避免 disabled、unknown、pass-through key 的第二次回调被误吞。6. 输入位置和 UI/托盘刷新去重
InputPositionCache,相同压缩坐标不重复发送WEASEL_IPC_UPDATE_INPUT_POS。Disconnect、StartSession、EndSession、维护模式切换后 reset 缓存,避免新 session 漏发首个相同坐标。RimeTrayIconSignature,只有影响托盘状态的字段实际变化时才刷新。_Respond和_UpdateUI复用 Rime status/context 快照,减少一次按键内重复get_status/get_context。Hide/Update。ascii_mode/!ascii_mode的候选窗内联图标提示;Shift 中英切换不再在输入位置弹出只含图标的状态窗,部署、方案、全半角等其他通知不受影响。7. 服务通知和托盘提示补齐
EndMaintenance(result)回传给 server,并显示托盘气泡。EndMaintenance(result)的部署结果通知不再被m_disabled门控;即使维护状态没有成功进入,也不会吞掉部署结果提示。SyncUserData()失败早退前会尝试EndMaintenance(),避免 server 停留在 disabled/维护状态。WEASEL_IPC_NOTIFY_SERVICE和 shutdown notification 不再从 pipe worker 线程直接操作托盘,改为PostMessage(WM_WEASEL_SERVICE_NOTIFY)收口到 server 消息线程。OnServiceNotifyMessage内部持g_api_mutex读取 handler,避免和系统结束会话时的 finalize/置空并发。8. 退出、重启、恢复语义修正
/recover在 manual-exit flag 存在时不启动服务,避免用户手动退出后被自动恢复拉起。/restart-manual和/startup会清除 manual-exit flag,用于明确的手动恢复。/restart只在明确来自交互入口时兼容成手动重启;父进程判断从黑名单改为白名单。/stop//stop-service,安装器、卸载器和 stop 脚本使用/stop,避免安装/卸载流程污染 manual-exit flag。WeaselService::Stop()改用WEASEL_IPC_SHUTDOWN_REASON_STOP,不再写 manual-exit flag。9. 安装和 IPC 稳定性修复
copy_file()改为安全替换:dest.new。FindSession()改为map.find()无副作用查询,不再通过operator[]插入 stale session。_UpdateUI(0)不再创建 session 0,也不再对 session 0 调 Rime option。PipeChannel::_Send()重连后使用新 pipe handle 重发,不再拿旧 handle 重发。PipeChannelBase::_Connect()在SetNamedPipeHandleState失败时关闭已打开 pipe,避免句柄泄漏。PipeServer::Listen()创建 per-connection worker 后显式detach(),避免局部boost::thread析构时留下 joinable 线程风险。10. 热路径日志和清理
ShouldTraceKeyEvents()提到WeaselUtility.h并缓存环境变量结果。WEASEL_TRACE_KEY_EVENTS关闭时不再构造每键 trace 字符串。!handled && has_commit的每键 debug log 改为受WEASEL_TRACE_KEY_EVENTS门控。idea-keytrace-20260521调试标记。output/install.bat里 IMM 时代遗留的regsvr32注释。imesetup.cpp的 IMM 注释改成当前 TSF-only 行为。include/resource.h的_APS_NEXT_COMMAND_VALUE与WeaselServer/resource.h对齐。11. 测试补充
TestWeaselIPC /unit增加或更新覆盖:ShouldEatKeyEvent(TRUE, TRUE)。ShouldEatKeyEvent覆盖 composing 被当前键结束时仍应吃键。_status.composing清理。Remember()/ suppress 行为。ascii_mode/!ascii_mode内联提示抑制,确认不会误伤部署、方案、全半角通知。/restart父进程白名单、/stop、manual-exit flag 语义。TryConnect()快速失败。预期效果
行为取舍和残余风险
WriteFile/ReadFileround-trip。本轮通过服务端 key path try-lock 处理最直接的g_api_mutex阻塞源;如果 Rime 在真正处理某个 key 时长时间卡住,仍可能等待该次响应。彻底解决需要 overlapped I/O + timeout/cancel 的更大 IPC 改造。_UpdateUICallback/ tray refresh 的数据流还没有整体迁到 UI 线程快照模式。本轮只修了NotifyService(DWORD)这条跨线程托盘气泡路径;Refresh()涉及UIStyle/Status中的字符串引用,不能只靠PostMessage简单搬线程。WeaselSetup::CustomInstall仍用后台线程串联/stop、/restart-manual、/deploy,保留固定 sleep。要彻底消除 sleep,需要新增“等待新 server ready 后再 deploy”的专用命令或 IPC handshake。验证
已执行:
结果:
TestWeaselIPC.exe /unit输出No errors detected.。WeaselServerx64/Win32、WeaselTSFx64/Win32、WeaselSetupWin32、WeaselDeployerx64/Win32 均构建成功。git diff --check无 whitespace error,只输出当前工作区 LF/CRLF 归一化提示。