Skip to content

[qa] QA recorder cpal 设备失败时永久卡 Recording 状态无错误反馈 #168

@appergb

Description

@appergb

现象

QA 录音过程中,若麦克风设备中途消失(拔 USB 麦克 / 系统切默认设备 / cpal 内部 panic):

  • QA 浮窗一直显示"录音中..."
  • 用户按 Option 第二次"提交"也没反应(phase 仍 Recording 但实际 stream 已死)
  • 必须按 Esc 关浮窗才能脱困

主听写(dictation)走的是另一条路径,有 `spawn_recorder_error_monitor` 监听 cpal runtime 错误并 emit 错误胶囊;QA 路径没有对应监听器。

复现

  1. 选段文字 → Cmd+Shift+; 浮窗 → Option 录音
  2. 录音中拔掉默认麦克风(或在系统设置切默认麦克)
  3. 观察:浮窗持续显示"录音中";后端日志可能有 cpal stream 错误,但前端无任何反馈

根因

coordinator.rs:1755 的 QA recorder 启动:

```rust
match Recorder::start(consumer, level_handler) {
Ok((rec, _runtime_errors)) => { // ← 把 errors mpsc receiver 直接 _ 弃用
*inner.qa_recorder.lock() = Some(rec);
}
Err(e) => {
// 启动失败有处理
}
}
```

对比主听写(coordinator.rs:1016):

```rust
Ok((rec, runtime_errors)) => {
*inner.recorder.lock() = Some(rec);
spawn_recorder_error_monitor(inner, runtime_errors); // ← QA 没有这一步
...
}
```

spawn_recorder_error_monitor 守护 mpsc receiver,收到 runtime error 时 emit `CapsuleState::Error` + finish session。QA 完全跳过了这步,所以 QA 录音中途如果 cpal 流出错,错误就消失在虚空里。

影响

  • 平台:三平台(cpal 跨平台,所有 OS 都可能遇 device error)
  • 频率:相对低(要求中途设备 disconnect / cpal panic),但当出现时无任何 recovery 路径
  • 用户损失:浮窗卡死直到手动关闭;用户不知道是 OpenLess 卡了还是麦克风问题

建议 fix

实现一个 `spawn_qa_recorder_error_monitor`,跟 `spawn_recorder_error_monitor` 同模式但走 QA 收尾路径:

```rust
fn spawn_qa_recorder_error_monitor(inner: &Arc, errors: mpsc::Receiver) {
let inner_clone = Arc::clone(inner);
let captured_session_id = inner.qa_state.lock().session_id;
std::thread::Builder::new()
.name("openless-qa-recorder-error-monitor".into())
.spawn(move || {
for err in errors {
let cur = inner_clone.qa_state.lock().session_id;
if cur != captured_session_id {
return; // stale,已是新 session
}
log::error!("[coord] QA recorder runtime error: {err}");
async_runtime::spawn(async move {
finish_qa_with_error(&inner_clone, format!("录音设备异常:{err}")).await;
});
return;
}
})
.ok();
}
```

然后在 `begin_qa_session` Recorder::start 成功后 spawn 它:

```rust
match Recorder::start(consumer, level_handler) {
Ok((rec, runtime_errors)) => {
*inner.qa_recorder.lock() = Some(rec);
spawn_qa_recorder_error_monitor(inner, runtime_errors); // 新增
}
...
}
```

session_id 守卫确保多轮提问的旧 monitor 不会乱炸新 session。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions