From bfc510e1ebc97338a0310c53ab5de3871ebde1b9 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 7 Mar 2026 09:25:08 +0100 Subject: [PATCH] [fix](trx-client,trx-server): fix spectrum death and stuck shutdown after IQ overflow Two bugs triggered by a SoapySDR IQ overflow: 1. Spectrum dies permanently (trx-client): when GetSpectrum times out (300ms SPECTRUM_IO_TIMEOUT), the error was silently swallowed and the spectrum buffer cleared. The in-flight response remained in the TCP receive buffer, desynchronising all subsequent reads so every poll kept failing. Fix: propagate the error so handle_connection returns and the outer loop reconnects, restoring TCP sync. 2. CTRL+C hangs trx-server: after IQ overflow, the sdr-iq-read thread can get stuck in a blocking SoapySDR/USB call (deactivate/activate with no timeout). Tokio received SIGINT and aborted async tasks, but the process could not exit while the native thread was blocked in uninterruptible I/O. Fix: call std::process::exit(0) after the graceful shutdown sequence so the OS forcibly terminates all threads. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Stan Grams --- src/trx-client/src/remote_client.rs | 8 ++++++-- src/trx-server/src/main.rs | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/trx-client/src/remote_client.rs b/src/trx-client/src/remote_client.rs index 16ffe66..a85cf7d 100644 --- a/src/trx-client/src/remote_client.rs +++ b/src/trx-client/src/remote_client.rs @@ -183,11 +183,15 @@ async fn handle_connection( guard.replace(snapshot.spectrum); } } - Err(_) => { - // Backend may not support spectrum; clear buffer silently. + Err(e) => { + // A spectrum poll failure desynchronises the TCP stream + // (the in-flight response is still in the buffer). + // Propagate the error so the caller reconnects and + // restores protocol sync. if let Ok(mut guard) = config.spectrum.lock() { guard.replace(None); } + return Err(e); } } } diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index b90f5ef..234fe2c 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -1017,5 +1017,7 @@ async fn main() -> DynResult<()> { for handle in task_handles { let _ = handle.await; } - Ok(()) + // Force exit so that native threads stuck in blocking hardware I/O + // (e.g. SoapySDR/USB transfers in D-state) cannot prevent shutdown. + std::process::exit(0); }