From 1001786a0818ef3369ef43f4c3f0318e0f4a4677 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 7 Mar 2026 09:31:17 +0100 Subject: [PATCH] [fix](trx-backend-soapysdr): ensure IQ stream recovery after overflow Two problems prevented reliable recovery after a persistent overflow: 1. Restart storm: once read_error_streak >= 3, every subsequent read failure triggered a deactivate/activate cycle, potentially preventing the hardware from stabilising. Fix: after a successful restart, reset read_error_streak to 1 so the stream gets 2 more failures before the next restart attempt. 2. Stuck-deactivated stream: if activate() failed after overflow, the stream was left deactivated. Subsequent reads returned non-overflow errors which handle_read_error ignored (Ok(false)), so the stream was never reactivated. Fix: add a high-streak fallback (>= 10 consecutive errors of any kind) that also attempts a full deactivate/activate restart, covering the stuck-deactivated case. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Stan Grams --- .../trx-backend-soapysdr/src/dsp.rs | 7 ++++ .../src/real_iq_source.rs | 36 ++++++++++++------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs index c549e39..deaa459 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs @@ -246,6 +246,7 @@ fn iq_read_loop( read_error_streak = read_error_streak.saturating_add(1); let err_lc = e.to_ascii_lowercase(); let is_overflow = err_lc.contains("overflow") || err_lc.contains("overrun"); + let restarted = read_error_streak >= 3; let recovered = match source.handle_read_error(&e, read_error_streak) { Ok(result) => result, Err(recovery_err) => { @@ -256,6 +257,12 @@ fn iq_read_loop( false } }; + // After a stream restart, reset the streak so we don't + // immediately trigger another restart on the first transient + // failure during hardware stabilisation. + if restarted && recovered { + read_error_streak = 1; + } if is_overflow { let now = Instant::now(); if overflow_log_window_start diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs index b12945e..3177e3f 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs @@ -182,23 +182,35 @@ impl IqSource for RealIqSource { fn handle_read_error(&mut self, err: &str, streak: u32) -> Result { let err_lc = err.to_ascii_lowercase(); let is_overrun = err_lc.contains("overflow") || err_lc.contains("overrun"); - if !is_overrun { + + if is_overrun { + // Overflow is often transient; avoid immediate stream restart churn. + // Only restart after several consecutive overflow failures. + if streak < 3 { + return Ok(true); + } + tracing::warn!( + "SoapySDR RX overflow persists (streak={}); restarting RX stream", + streak + ); + } else if streak >= 10 { + // Non-overflow errors at a high streak (e.g. reads on a + // deactivated stream after a failed activate) — attempt a + // full restart to recover. + tracing::warn!( + "SoapySDR RX persistent error (streak={}): {}; restarting RX stream", + streak, + err + ); + } else { return Ok(false); } - // Overflow is often transient; avoid immediate stream restart churn. - // Only restart after several consecutive read failures. - if streak < 3 { - return Ok(true); - } - tracing::warn!( - "SoapySDR RX overflow persists (streak={}); restarting RX stream", - streak - ); + let _ = self.stream.deactivate(None); - std::thread::sleep(std::time::Duration::from_millis(25)); + std::thread::sleep(std::time::Duration::from_millis(50)); self.stream .activate(None) - .map_err(|e| format!("Failed to reactivate RX stream after overflow: {}", e))?; + .map_err(|e| format!("Failed to reactivate RX stream: {}", e))?; Ok(true) } }