现象
QA 录音过程中,若麦克风设备中途消失(拔 USB 麦克 / 系统切默认设备 / cpal 内部 panic):
- QA 浮窗一直显示"录音中..."
- 用户按 Option 第二次"提交"也没反应(phase 仍 Recording 但实际 stream 已死)
- 必须按 Esc 关浮窗才能脱困
主听写(dictation)走的是另一条路径,有 `spawn_recorder_error_monitor` 监听 cpal runtime 错误并 emit 错误胶囊;QA 路径没有对应监听器。
复现
- 选段文字 → Cmd+Shift+; 浮窗 → Option 录音
- 录音中拔掉默认麦克风(或在系统设置切默认麦克)
- 观察:浮窗持续显示"录音中";后端日志可能有 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。
现象
QA 录音过程中,若麦克风设备中途消失(拔 USB 麦克 / 系统切默认设备 / cpal 内部 panic):
主听写(dictation)走的是另一条路径,有 `spawn_recorder_error_monitor` 监听 cpal runtime 错误并 emit 错误胶囊;QA 路径没有对应监听器。
复现
根因
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 流出错,错误就消失在虚空里。影响
建议 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。