2.6 KiB
RB-01 CRITICAL: racy single-session guard in live.rs
Severity: CRITICAL
Path: src-tauri/src/commands/live.rs:193-338
Source: 2026-04-22 code review
Labels: release-blocker, critical, concurrency
Status: RESOLVED (2026-04-22)
Resolution
LiveTranscriptionState now includes a dedicated
tokio::sync::Mutex<()> lifecycle gate. Both
start_live_transcription_session and
stop_live_transcription_session acquire that async mutex before
touching running, and they keep it held across the awaited setup /
join work that previously exposed the race windows.
That changes the two failing interleavings from the review:
- Two overlapping starts no longer race through the empty-slot check.
The second call waits for the first to finish setup, then observes
running.is_some()and returns the existing"A live transcription session is already running"error. - A start launched during stop can no longer sneak in after
running.take()but before the previous worker has fully joined. It blocks on the lifecycle mutex until the join completes.
Regression tests in commands::live::tests:
concurrent_starts_allow_only_one_session_to_claim_the_slotstart_waits_for_stop_to_finish_joining_before_reusing_slot
Problem
start_live_transcription_session checks running is None before multiple awaits and only stores the handle at the end. stop_live_transcription_session removes running before awaiting the worker join. Two overlapping IPC calls can:
- Admit a second live session (start sees
running == None, awaits, another start fires in the gap, both proceed) - Expose an empty slot while the first session is still shutting down (stop removes the handle, awaits, a fresh start runs against the incoherent state)
This breaks the file's core invariant that only one microphone/live session exists at a time.
Acceptance
- Hold the session-slot lock (or a semaphore) across the async boundary so no two
start/stopIPC calls can interleave. - Regression test: fire two
start_live_transcription_sessionIPC calls concurrently; exactly one must succeed and the other must error cleanly. - Regression test: during an in-flight
stop, a concurrentstartmust block until the previous session's worker has fully joined.
Fix scope
Large. Will likely require the run_live_session monolith refactor (RB-04) to land first so the state machine is small enough to reason about under the lock discipline.
Dependencies
- Landed after RB-04 (
run_live_sessionrefactor) made the worker lifecycle explicit enough to guard safely.