fix: guard Foundry release and cancel stale transcripts

This commit is contained in:
millionart committed 2026-05-07 10:08:36 +08:00
1 parent 2290a6cbee
commit a54ca77e9b
2 files changed
+52 -3

No files matched your search

@@ -6,6 +6,7 @@ use std::fs::{self, OpenOptions};
use std::io::Write;
#[cfg(target_os = "windows")]
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(target_os = "windows")]
use std::sync::Arc;
@@ -28,6 +29,7 @@ pub struct FoundryLocalWhisperAsr {
model_alias: String,
language_hint: Option<String>,
buffer: Mutex<Vec<u8>>,
cancel_generation: AtomicU64,
}
impl FoundryLocalWhisperAsr {
@@ -42,6 +44,7 @@ impl FoundryLocalWhisperAsr {
model_alias,
language_hint: normalize_language_hint(language_hint),
buffer: Mutex::new(Vec::new()),
cancel_generation: AtomicU64::new(0),
}
}
@@ -51,6 +54,7 @@ impl FoundryLocalWhisperAsr {
model_alias,
language_hint: normalize_language_hint(language_hint),
buffer: Mutex::new(Vec::new()),
cancel_generation: AtomicU64::new(0),
}
}
@@ -63,6 +67,7 @@ impl FoundryLocalWhisperAsr {
}
pub async fn transcribe(&self, audio_timeout: std::time::Duration) -> Result<RawTranscript> {
let cancel_generation = self.cancel_generation.load(Ordering::SeqCst);
let pcm = self.buffer.lock().clone();
if pcm.is_empty() {
return Ok(RawTranscript {
@@ -72,6 +77,9 @@ impl FoundryLocalWhisperAsr {
}
let result = self.transcribe_inner(&pcm, audio_timeout).await;
if self.cancel_generation.load(Ordering::SeqCst) != cancel_generation {
anyhow::bail!("Foundry Local Whisper transcription cancelled");
}
if result.is_ok() {
self.buffer.lock().clear();
}
@@ -121,6 +129,7 @@ impl FoundryLocalWhisperAsr {
}
pub fn cancel(&self) {
self.cancel_generation.fetch_add(1, Ordering::SeqCst);
self.buffer.lock().clear();
}
}
@@ -298,6 +307,12 @@ mod tests {
provider.cancel();
assert!(provider.buffer.lock().is_empty());
assert_eq!(
provider
.cancel_generation
.load(std::sync::atomic::Ordering::SeqCst),
1
);
assert_eq!(provider.model_alias(), "whisper-small");
assert_eq!(provider.language_hint(), Some("zh"));
}
+37 -3
View File
@@ -2522,6 +2522,15 @@ async fn end_session(inner: &Arc<Inner>) -> Result<(), String> {
r
}
Err(e) => {
if inner.state.lock().cancelled {
log::info!(
"[coord] Foundry Local Whisper transcribe cancelled — discarding transcript"
);
schedule_foundry_local_asr_release(inner, current_session_id);
restore_prepared_windows_ime_session(inner, current_session_id);
set_phase_idle_if_session_matches(inner, current_session_id);
return Ok(());
}
log::error!("[coord] Foundry Local Whisper transcribe failed: {e:#}");
emit_capsule(
inner,
@@ -3179,6 +3188,11 @@ fn foundry_local_asr_release_keep_secs(inner: &Arc<Inner>) -> u32 {
inner.prefs.get().foundry_local_asr_keep_loaded_secs
}
#[cfg(target_os = "windows")]
fn foundry_release_session_is_current(inner: &Arc<Inner>, session_id: u64) -> bool {
inner.state.lock().session_id == session_id
}
#[cfg(target_os = "windows")]
fn schedule_foundry_local_asr_release(inner: &Arc<Inner>, session_id: u64) {
let keep_secs = foundry_local_asr_release_keep_secs(inner);
@@ -3187,9 +3201,9 @@ fn schedule_foundry_local_asr_release(inner: &Arc<Inner>, session_id: u64) {
tauri::async_runtime::spawn(async move {
if keep_secs > 0 {
tokio::time::sleep(std::time::Duration::from_secs(keep_secs as u64)).await;
if inner.state.lock().session_id != session_id {
return;
}
}
if !foundry_release_session_is_current(&inner, session_id) {
return;
}
if let Err(error) = runtime.release_now().await {
log::warn!("[foundry-asr] scheduled release failed: {error:#}");
@@ -4064,6 +4078,26 @@ mod tests {
assert_eq!(foundry_local_asr_release_keep_secs(&coordinator.inner), 7);
}
#[cfg(target_os = "windows")]
#[test]
fn foundry_release_guard_rejects_stale_session() {
let runtime = Arc::new(crate::asr::local::FoundryLocalRuntime::new());
let coordinator = Coordinator::new_with_foundry_runtime(runtime);
let old_session_id = coordinator.inner.state.lock().session_id;
assert!(foundry_release_session_is_current(
&coordinator.inner,
old_session_id
));
coordinator.inner.state.lock().session_id = old_session_id.wrapping_add(1);
assert!(!foundry_release_session_is_current(
&coordinator.inner,
old_session_id
));
}
#[test]
fn resolve_ark_endpoint_rejects_blank_key_without_custom_endpoint() {
assert_eq!(