From 0421ced5bee19634bf7f922e60bbf8840a8b3dd3 Mon Sep 17 00:00:00 2001 From: millionart Date: Wed, 6 May 2026 23:06:38 +0800 Subject: [PATCH] fix: split Foundry prepare and transcription timeouts --- .../src/asr/local/foundry_provider.rs | 17 +++++-- .../src/asr/local/foundry_runtime.rs | 11 ++++- openless-all/app/src-tauri/src/coordinator.rs | 47 ++++++------------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/openless-all/app/src-tauri/src/asr/local/foundry_provider.rs b/openless-all/app/src-tauri/src/asr/local/foundry_provider.rs index 747ea997..a951d978 100644 --- a/openless-all/app/src-tauri/src/asr/local/foundry_provider.rs +++ b/openless-all/app/src-tauri/src/asr/local/foundry_provider.rs @@ -62,7 +62,7 @@ impl FoundryLocalWhisperAsr { self.language_hint.as_deref() } - pub async fn transcribe(&self) -> Result { + pub async fn transcribe(&self, audio_timeout: std::time::Duration) -> Result { let pcm = self.buffer.lock().clone(); if pcm.is_empty() { return Ok(RawTranscript { @@ -71,14 +71,18 @@ impl FoundryLocalWhisperAsr { }); } - let result = self.transcribe_inner(&pcm).await; + let result = self.transcribe_inner(&pcm, audio_timeout).await; if result.is_ok() { self.buffer.lock().clear(); } result } - async fn transcribe_inner(&self, pcm: &[u8]) -> Result { + async fn transcribe_inner( + &self, + pcm: &[u8], + audio_timeout: std::time::Duration, + ) -> Result { let duration_ms = pcm_duration_ms(pcm); #[cfg(not(target_os = "windows"))] @@ -95,7 +99,12 @@ impl FoundryLocalWhisperAsr { let wav_file = TempWavFile::create(pcm)?; let text = self .runtime - .transcribe_audio_file(&self.model_alias, self.language_hint(), wav_file.path()) + .transcribe_audio_file( + &self.model_alias, + self.language_hint(), + wav_file.path(), + audio_timeout, + ) .await .with_context(|| { format!( diff --git a/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs b/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs index 367fd5ce..de4d0777 100644 --- a/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs +++ b/openless-all/app/src-tauri/src/asr/local/foundry_runtime.rs @@ -129,6 +129,7 @@ mod imp { alias: &str, language_hint: Option<&str>, audio_path: &Path, + audio_timeout: std::time::Duration, ) -> Result { let _lifecycle = self.lifecycle.lock().await; self.cancel_prepare.store(false, Ordering::SeqCst); @@ -140,9 +141,14 @@ mod imp { if let Some(language_hint) = normalized_language_hint(language_hint) { client = client.language(language_hint); } - let result = client - .transcribe(audio_path) + let result = tokio::time::timeout(audio_timeout, client.transcribe(audio_path)) .await + .with_context(|| { + format!( + "transcribe audio with Foundry model {alias} timed out after {} seconds", + audio_timeout.as_secs() + ) + })? .with_context(|| format!("transcribe audio with Foundry model {alias}"))?; Ok(result.text) } @@ -448,6 +454,7 @@ impl FoundryLocalRuntime { alias: &str, _language_hint: Option<&str>, _audio_path: &std::path::Path, + _audio_timeout: std::time::Duration, ) -> anyhow::Result { anyhow::bail!("Foundry Local Whisper is only available on Windows: {alias}"); } diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 1bf48b88..c67fdb34 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -1716,13 +1716,15 @@ async fn end_session(inner: &Arc) -> Result<(), String> { #[cfg(target_os = "windows")] ActiveAsr::FoundryLocalWhisper(local) => { debug_assert!(!uses_global_timeout); - let timeout_duration = foundry_transcribe_timeout_duration(); - match tokio::time::timeout(timeout_duration, local.transcribe()).await { - Ok(Ok(r)) => { + match local + .transcribe(foundry_audio_transcribe_timeout_duration()) + .await + { + Ok(r) => { schedule_foundry_local_asr_release(inner, current_session_id); r } - Ok(Err(e)) => { + Err(e) => { log::error!("[coord] Foundry Local Whisper transcribe failed: {e:#}"); emit_capsule( inner, @@ -1737,26 +1739,6 @@ async fn end_session(inner: &Arc) -> Result<(), String> { schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); return Err(e.to_string()); } - Err(_) => { - log::error!( - "[coord] Foundry Local Whisper timeout after {} seconds", - FOUNDRY_LOCAL_TRANSCRIBE_TIMEOUT_SECS - ); - local.cancel(); - schedule_foundry_local_asr_release(inner, current_session_id); - emit_capsule( - inner, - CapsuleState::Error, - 0.0, - elapsed, - Some("本地识别超时".to_string()), - None, - ); - restore_prepared_windows_ime_session(inner, current_session_id); - inner.state.lock().phase = SessionPhase::Idle; - schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); - return Err("foundry local timeout".to_string()); - } } } #[cfg(target_os = "macos")] @@ -3256,11 +3238,13 @@ mod tests { #[cfg(target_os = "windows")] #[test] - fn foundry_transcribe_has_finite_extended_timeout() { - let timeout = foundry_transcribe_timeout_duration(); + fn foundry_audio_transcribe_timeout_is_separate_from_prepare() { + let timeout = foundry_audio_transcribe_timeout_duration(); - assert!(timeout > std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS)); - assert!(timeout <= std::time::Duration::from_secs(15 * 60)); + assert_eq!( + timeout, + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) + ); } #[test] @@ -3579,11 +3563,8 @@ const CAPSULE_AUTO_HIDE_DELAY_MS: u64 = 2000; const COORDINATOR_GLOBAL_TIMEOUT_SECS: u64 = 15; #[cfg(target_os = "windows")] -const FOUNDRY_LOCAL_TRANSCRIBE_TIMEOUT_SECS: u64 = 10 * 60; - -#[cfg(target_os = "windows")] -fn foundry_transcribe_timeout_duration() -> std::time::Duration { - std::time::Duration::from_secs(FOUNDRY_LOCAL_TRANSCRIBE_TIMEOUT_SECS) +fn foundry_audio_transcribe_timeout_duration() -> std::time::Duration { + std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS) } /// begin_session 中各 await 之间的 cancel race 检查结果。