diff --git a/src-tauri/src/commands/live.rs b/src-tauri/src/commands/live.rs index 19bcfaa..df33c7a 100644 --- a/src-tauri/src/commands/live.rs +++ b/src-tauri/src/commands/live.rs @@ -702,13 +702,19 @@ pub async fn start_live_transcription_session( status_channel: Channel, ) -> Result { ensure_main_window(&window)?; - // Phase 1: acquire the lifecycle lock long enough to reserve the - // single live-session slot. Held across `ensure_model_loaded` - // because we want start/stop concurrency to remain serialised; the - // dangerous pattern (lock held across a JoinHandle.await) was on - // the stop path, not here. Released explicitly before the - // RunningLiveSession is installed in `live_state.running` so the - // symmetric stop path doesn't observe a half-initialised state. + // Phase 1: acquire the lifecycle lock and hold it across the full + // startup sequence — model load, audio-path resolution, worker + // spawn, AND installation of the RunningLiveSession in + // `live_state.running`. The lock is released by the explicit drop + // in Phase 2 (see comment near the bottom of this function). + // + // Holding through the install is intentional: a concurrent + // `stop_live_transcription_session` acquiring lifecycle MUST + // observe either a fully-installed `running` slot or none at all, + // never a half-initialised state. The dangerous pattern of holding + // the lock across a `JoinHandle.await` is on the stop path, not + // here — start hands the handle off to RunningLiveSession and + // never awaits it from inside this function. let lifecycle = live_state.lifecycle.lock().await; { let running = live_state.running.lock().unwrap();