{"record":{"id":"ec8bc7d2a0202039","repo":"cjpais/Handy","slug":"timed-out-waiting-for-live-transcription-to-f","errorCode":null,"errorMessage":"Timed out waiting {:?} for live transcription to finalize","messagePattern":"Timed out waiting (.+?) for live transcription to finalize","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/transcription.rs","lineNumber":1103,"sourceCode":"    /// `Ok(None)` means no usable stream was active and the caller may fall back\n    /// to batch transcription. `Err` means finalize itself failed or timed out.\n    /// A timeout may still leave the worker holding the engine, so callers\n    /// should surface it instead of immediately starting a batch fallback.\n    pub fn finalize_stream(&self) -> Result<Option<String>> {\n        let Some(tx) = self.router.take() else {\n            return Ok(None);\n        };\n        let (reply_tx, reply_rx) = mpsc::channel();\n        if tx.send(StreamCmd::Finalize(reply_tx)).is_err() {\n            return Ok(None);\n        }\n        let finalized = match reply_rx.recv_timeout(STREAM_FINALIZE_REPLY_TIMEOUT) {\n            Ok(Some(finalized)) => finalized,\n            Ok(None) => return Ok(None),\n            Err(mpsc::RecvTimeoutError::Disconnected) => return Ok(None),\n            Err(mpsc::RecvTimeoutError::Timeout) => {\n                self.stream_active.store(false, Ordering::Release);\n                return Err(anyhow::anyhow!(\n                    \"Timed out waiting {:?} for live transcription to finalize\",\n                    STREAM_FINALIZE_REPLY_TIMEOUT\n                ));\n            }\n        };\n\n        let settings = get_settings(&self.app_handle);\n        // Streaming models do not receive a decode prompt, so custom words\n        // always go through the shared fuzzy post-correction path.\n        let filtered = post_process_transcription_text(\n            finalized.text,\n            &settings,\n            false,\n            &finalized.output_language,\n            &finalized.supported_languages,\n        );\n\n        self.maybe_unload_immediately(\"streaming transcription\");","sourceCodeStart":1085,"sourceCodeEnd":1121,"githubUrl":"https://github.com/cjpais/Handy/blob/98a4d80cce8ad41efec2a419b59d9e81229a35d7/src-tauri/src/managers/transcription.rs#L1085-L1121","documentation":"finalize_stream() sends StreamCmd::Finalize to the streaming worker thread and blocks on reply_rx.recv_timeout(STREAM_FINALIZE_REPLY_TIMEOUT) (30s). A Timeout means the worker accepted the command but did not reply in time — typically it is still draining queued Feed commands or is stuck inside stream.finalize() on very long buffered audio or a stalled GPU. Handy clears stream_active and returns this error; the doc comment warns the worker may still hold the engine, so callers must surface the error rather than immediately starting a batch fallback.","triggerScenarios":"User stops a long live-transcription session whose queued PCM backlog plus stream.finalize() compute exceeds 30s; stream.finalize() hangs on a GPU backend (Vulkan/Metal device lost, driver stall); the worker thread is starved by CPU contention while processing the Feed queue ahead of the Finalize command.","commonSituations":"Multi-minute dictations on CPU-only or low-end machines; GPU driver crash/hang mid-session; heavy parallel system load during finalize; very large feed backlog accumulated because feeds outpaced inference.","solutions":["Retry stopping/finalizing once after a short pause — the worker often completes and a second attempt succeeds on the re-established stream","Shorten live sessions and stop promptly so the finalize backlog stays small","Switch the accelerator (or model) to one that keeps real-time factor comfortably below 1","Update GPU drivers / check for Vulkan-Metal stalls (on Linux, VK_LOADER_DEBUG=error reveals loader faults)","As a maintainer: raise STREAM_FINALIZE_REPLY_TIMEOUT or drain feeds before finalizing"],"exampleFix":"// before\nlet text = tm.finalize_stream()?; // single shot, 30s cap\n\n// after — retry once, and never batch-fallback immediately on timeout\nlet text = match tm.finalize_stream() {\n    Ok(Some(t)) => t,\n    Ok(None) => tm.transcribe(audio)?, // no live stream: batch fallback is safe\n    Err(e) if e.to_string().contains(\"Timed out\") => {\n        thread::sleep(Duration::from_secs(2));\n        tm.finalize_stream()?.unwrap_or_default() // retry; surface if it fails again\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":"// Cheap pre-checks: only finalize when a stream router exists and is active\nif !tm.stream_is_active() { return batch_fallback(); } // no live stream — skip the handshake","typeGuard":null,"tryCatchPattern":"match tm.finalize_stream() {\n    Ok(Some(text)) => text,\n    Ok(None) => tm.transcribe(audio)?, // no usable stream — batch fallback is safe here\n    Err(e) if e.to_string().contains(\"Timed out\") => {\n        // Worker may still hold the engine: surface to the user, retry once, do NOT batch-fallback immediately\n        user_notify(\"Finalizing is taking longer than expected…\");\n        tm.finalize_stream()?.unwrap_or_default()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Stop live sessions promptly instead of letting multi-minute backlogs accumulate","Pick an accelerator/model whose real-time factor stays well below 1 for streaming","Keep GPU drivers current; on Linux use VK_LOADER_DEBUG=error to catch Vulkan stalls early","Watch stream perf logs (feed/compute timings) and end sessions when compute lags feed persistently"],"tags":["rust","streaming","timeout","asr","mpsc","finalize"],"backgroundTag":"channel-recv-timeout","analyzedSha":"98a4d80cce8ad41efec2a419b59d9e81229a35d7","analyzedAt":"2026-08-16T20:58:09.966Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}