热键 bridge 串行阻塞:一次慢 IO 就能让热键彻底失灵(已为此挖了三条旁路)

Đang mở
#872 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
12/100
Loại issue
Tái cấu trúc
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Ít trao đổi
Công nghệ
rust, tauri
Lĩnh vực
desktop, performance

Hướng nghiên cứu

Read hotkey_bridge_loop and the async_runtime::block_on work it drives, then the three bypasses it spawned: the Esc channel (#853), the combo-cancel channel (#858), and HOTKEY_QUEUE_GRACE_MS / is_queued_chain_press. Read #468/#475 first, since they explain why Pressed/Released ordering must stay serial, and #871 for the current 5s timeouts. Done means latch/phase transitions still follow physical key order while begin_session / end_session leave the bridge thread, backed by tests that key-edge ordering is unchanged.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

P3

现象

热键的按下 / 松开事件走一条串行 bridge 线程(hotkey_bridge_loop,async_runtime::block_on 逐个处理)。而这条线程上跑的是开麦克风、连 ASR、转写、LLM 润色这些慢活。

结果是:这条线程上任何一次慢 IO,都会让热键在此期间完全失去响应 —— 按下开不了录音,松开也停不了,且没有任何 UI 提示,用户只会认为「热键坏了」。

实测(macOS,StepFun 实时 ASR):连续开会话时一次 WebSocket 建连没有返回,之后热键完全无响应,只能退出 app 重开。日志里 tap 线程仍在正常记录按键,只是没人处理:

07:20:14.201  [hotkey] 触发键与其他键组合按下 —— 撤销本次触发     ← tap 活着
07:20:15.985  [hotkey] 触发键与其他键组合按下 —— 撤销本次触发     ← tap 活着
                                                    ↑ 但没有任何 [coord] hotkey pressed

sample 确认 openless-hotkey-bridge 停在 __psynch_cvwait(block_on 等 future),主线程与 tap 线程都正常。

#871 给三个供应商的建连补了 5s 超时,把「永远死」降级成「死 5 秒」。但那是止血,不是修复。

这个结构已经反复付出代价

它不是一个假设中的隐患 —— 目前代码里至少有三处逻辑,存在的唯一理由就是绕开这条线程会堵:

  1. Esc 取消专用通道(#853)。转写 + 润色在 bridge 线程上同步跑,Esc 若与 Pressed/Released 同队列就只能排在后面,等流程结束才被取出,此时 phase 已回 Idle、cancel 变 no-op —— 用户按 Esc 毫无反应。修法是给 Esc 单开一条 Sender<()> + 专用消费线程。

  2. 组合键撤销专用通道(#858)。同一个病:撤销事件排在这次按下自己的 begin_session 后面,胶囊要等开麦跑完才消失(实测差 0.8~1s)。又单开一条通道。

  3. 「排队接力」判定(HOTKEY_QUEUE_GRACE_MS = 120,is_queued_chain_press)。转写期间的按下被缓在 channel 里,会话收尾后才取出;需要额外逻辑区分「刚才排队的那次按下」和「用户新按的」,以免与 #545 的冷却保护打架。

每加一条旁路,就多一组「两条通道之间谁先谁后」的竞态要考虑。#858 里已经为此加了回归测试(Released 抢先跑完时撤销仍须生效)。这是在给同一个病贴第 N 张膏药。

为什么不能简单地把串行撤掉

串行本身是 #468/#475 的解药:Pressed/Released 若并行执行,hotkey_trigger_held latch 的翻转顺序会乱(Windows 的 WH_KEYBOARD_LL 边沿间隔可到微秒级),表现为「录音停不下来」或「下次按键被静默吞掉」。代码注释里标为 P0。

所以这不是「删掉 block_on 就好」,而是要换一种方式同时满足:

  • 按键边沿的处理顺序必须严格跟随物理顺序;
  • 慢 IO(开麦 / 建连 / 转写 / 润色)不得占住边沿处理。

候选方向(列出来,不替维护者选)

  • A. 边沿处理与会话工作分离:bridge 只做纯同步的状态机迁移(latch、phase 判定),把 begin_session / end_session 派发到运行时,用 session id + 代次号丢弃过期结果。顺序由 bridge 保证,慢活不再占线程。
  • B. 保留串行,但给所有落在 bridge 上的 await 强制上限:本质是 #871 的推广,简单但治标;只要有人漏加一个超时就会复发。
  • C. 单一事件队列 + 优先级:把 Esc / 组合键撤销那些旁路收回同一条队列,用优先级而非独立通道,减少竞态面。不解决「慢活占线程」,但能止住旁路增殖。

各有代价,且都动到当初为修 #468/#475 而引入的部分,风险不低。

我的打算

暂不动手,等维护者定方向。这块是全仓库最容易出竞态的地方,我不想在方向未定时先改,那只会制造一处最难合并的分叉。

如果维护者认可某条路子,我可以按那个方向做,并先把按键顺序那部分单独抽出来补测试,确认改完顺序仍然正确再动线程。

相关:#853、#858、#871(均为绕开或止血),#468 / #475(串行的由来),#545(冷却保护,与排队接力互相牵扯)。

Ngôn ngữ chính
Rust
Star
3.7k
Fork
349
Merge trung bình
2 ngày 14 giờ
Pull request đã merge (30 ngày)
97

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của Open-Less/openless

Tất cả issue của Open-Less/openless

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.