agent: code-atomiser-fix — cancellable Whisper inference + bounded drain + lock-over-await
Three findings chain: thread::spawn discarded the JoinHandle and gave no cancellation route into the whisper backend; drain_inference busy- polled with no timeout; stop_live_transcription_session held the lifecycle AsyncMutex across the await of a non-cancellable spawn_blocking handle. Together: a single wedged inference (ggml deadlock, GPU stall) bricked every future start/stop until app restart. Fix: each inference task carries an Arc<AtomicBool> abort_flag; the flag is wired into whisper-rs::FullParams::set_abort_callback_safe so the spawned blocking thread checks it and exits cleanly. drain_inference is bounded by a deadline (3 x chunk_duration, min 2s); on expiry the flag is set, the receiver is dropped, and a typed Error::InferenceTimeout status is surfaced. Drop for InferenceTask asserts the abort flag so any '?'-propagation or panic unwind closes the cancellation route without relying on the explicit drain path. stop_live_transcription_session restructured so the lifecycle guard is dropped BEFORE the JoinHandle is awaited; start_live_transcription_session releases the guard explicitly after installing the RunningLiveSession. Unit test added: dropping_inference_task_sets_abort_flag covers the Race-A regression directly. A real Race-B drain-timeout test would require a wedged whisper-rs, which is hard to fixture; that path is covered by the SAFETY comment and cargo build + manual smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
//! `whisper` feature — `WhisperRsBackend` (direct whisper-rs, the only
|
||||
//! path that pipes `initial_prompt`).
|
||||
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lumotia_core::error::Result;
|
||||
use lumotia_core::types::{Segment, TranscriptionOptions};
|
||||
|
||||
@@ -45,6 +48,24 @@ pub trait Transcriber: Send {
|
||||
samples: &[f32],
|
||||
options: &TranscriptionOptions,
|
||||
) -> Result<Vec<Segment>>;
|
||||
|
||||
/// Variant of `transcribe_sync` that accepts an external abort flag.
|
||||
///
|
||||
/// Backends that can react to mid-inference cancellation (whisper-rs
|
||||
/// via `set_abort_callback_safe`) override this to poll the flag
|
||||
/// during decoding so a wedged inference can be unstuck by the live
|
||||
/// session's `drain_inference` timeout. The default implementation
|
||||
/// ignores the flag and dispatches to `transcribe_sync` so
|
||||
/// non-cancellable backends (Parakeet via transcribe-rs) keep their
|
||||
/// current behaviour.
|
||||
fn transcribe_sync_with_abort(
|
||||
&mut self,
|
||||
samples: &[f32],
|
||||
options: &TranscriptionOptions,
|
||||
_abort_flag: Arc<AtomicBool>,
|
||||
) -> Result<Vec<Segment>> {
|
||||
self.transcribe_sync(samples, options)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user