Isolate speech transport failures and keep experimental media usable

This commit is contained in:
karl committed 2026-10-13 21:09:43 +02:00
1 parent f15ee07ed0
commit 296a39a6c2
7 files changed
+147 -3

No files matched your search

+25 -2
View File
@@ -52,6 +52,7 @@ pub struct SpeechWorker {
base_path: PathBuf,
vad_path: PathBuf,
device: Device,
reset_on_next_source: bool,
}
fn verified_file(path: &Path, bytes: u64, expected: &str) -> Result<String> {
@@ -114,11 +115,31 @@ impl SpeechWorker {
base_path: base.to_owned(),
vad_path: vad.to_owned(),
device,
reset_on_next_source: false,
})
}
pub fn fingerprint(&self) -> &str {
&self.fingerprint
}
/// Recover a failed transport only at admission of a different source.
/// Never retries the previous source or malformed native output.
pub fn begin_source(&mut self) -> Result<()> {
if !std::mem::take(&mut self.reset_on_next_source) {
return Ok(());
}
let replacement = Self::load(
&self.worker_path,
&self.base_path,
&self.vad_path,
self.device,
)?;
ensure!(
replacement.fingerprint == self.fingerprint,
"Speech assets changed before source recovery"
);
*self = replacement;
Ok(())
}
pub fn transcribe(&mut self, pcm: &[f32], sample_rate: u32) -> Result<SpeechTranscription> {
ensure!(
sample_rate == SAMPLE_RATE && !pcm.is_empty() && pcm.len() <= MAX_SAMPLES,
@@ -126,7 +147,7 @@ impl SpeechWorker {
);
ensure!(pcm.iter().all(|v| v.is_finite()), "Nonfinite speech input");
let fallback_started = Instant::now();
match self.transcribe_once(pcm) {
let result = (|| match self.transcribe_once(pcm) {
Ok(value) => Ok(value),
Err(error) if self.device == Device::Auto && process::recoverable(&error) => {
// Retry only initialization/compute/transport failures. Reverify
@@ -148,7 +169,9 @@ impl SpeechWorker {
Ok(result)
}
Err(error) => Err(error),
}
})();
self.reset_on_next_source = result.as_ref().err().is_some_and(process::failed_transport);
result
}
fn transcribe_once(&mut self, pcm: &[f32]) -> Result<SpeechTranscription> {
let started = Instant::now();
+8
View File
@@ -60,6 +60,9 @@ pub(crate) fn recoverable(error: &anyhow::Error) -> bool {
.downcast_ref::<NativeFailure>()
.is_some_and(NativeFailure::recoverable)
}
pub(crate) fn failed_transport(error: &anyhow::Error) -> bool {
error.is::<TransportFailure>()
}
fn read_response<R: Read>(reader: &mut R) -> Result<Value> {
let mut prefix = [0; 4];
@@ -259,6 +262,7 @@ mod tests {
.exchange(vec![1], Duration::from_millis(50))
.unwrap_err();
assert!(recoverable(&error));
assert!(failed_transport(&error));
assert!(process.child.try_wait().unwrap().is_some());
assert!(process.failed);
let mut command = Command::new(&python);
@@ -268,6 +272,7 @@ mod tests {
.exchange(vec![1], Duration::from_secs(1))
.unwrap_err();
assert!(!recoverable(&error));
assert!(!failed_transport(&error));
assert!(process.child.try_wait().unwrap().is_some());
assert!(process.failed);
}
@@ -279,9 +284,12 @@ mod tests {
anyhow::Error::new(ProtocolFailure),
] {
assert!(!recoverable(&error));
assert!(!failed_transport(&error));
}
assert!(recoverable(&NativeFailure::Initialization.into()));
assert!(recoverable(&NativeFailure::Compute.into()));
assert!(!failed_transport(&NativeFailure::Initialization.into()));
assert!(!failed_transport(&NativeFailure::Compute.into()));
assert!(!recoverable(
&read_response(&mut &[2, 0, 0, 0, 0xff, 0xff][..]).unwrap_err()
));