From 0a7195c93c3c9c45e62540d7d5be0d594f05b733 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Fri, 27 Feb 2026 01:15:57 +0100 Subject: [PATCH] fix(trx-server): add shutdown signal to audio capture/playback threads Pass `watch::Receiver` into `run_capture` and `run_playback` so both threads check for shutdown at the top of their outer recovery loop and inner monitoring loop. Without this, restarting the process (e.g. via systemd after an ALSA error) left the old threads stalled forever while new ones were created alongside them. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Stan Grams --- src/trx-server/src/audio.rs | 29 ++++++++++++++++++++++++++++- src/trx-server/src/main.rs | 4 +++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/trx-server/src/audio.rs b/src/trx-server/src/audio.rs index c405acf..191f49f 100644 --- a/src/trx-server/src/audio.rs +++ b/src/trx-server/src/audio.rs @@ -241,6 +241,7 @@ pub fn spawn_audio_capture( cfg: &AudioConfig, tx: broadcast::Sender, pcm_tx: Option>>, + shutdown_rx: watch::Receiver, ) -> std::thread::JoinHandle<()> { let sample_rate = cfg.sample_rate; let channels = cfg.channels as u16; @@ -257,6 +258,7 @@ pub fn spawn_audio_capture( device_name, tx, pcm_tx, + shutdown_rx, ) { error!("Audio capture thread error: {}", e); } @@ -271,6 +273,7 @@ fn run_capture( device_name: Option, tx: broadcast::Sender, pcm_tx: Option>>, + shutdown_rx: watch::Receiver, ) -> Result<(), Box> { use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use std::sync::mpsc::{RecvTimeoutError, TryRecvError as StdTryRecvError}; @@ -305,6 +308,11 @@ fn run_capture( let mut capturing = false; loop { + if *shutdown_rx.borrow() { + info!("Audio capture: shutdown signal received, exiting"); + return Ok(()); + } + // Re-enumerate the device on every recovery cycle: after POLLERR the // existing device handle can be stale (especially for USB audio). let host = cpal::default_host(); @@ -383,6 +391,11 @@ fn run_capture( } loop { + if *shutdown_rx.borrow() { + info!("Audio capture: shutdown signal received, exiting"); + return Ok(()); + } + match stream_err_rx.try_recv() { Ok(()) | Err(StdTryRecvError::Disconnected) => { warn!("Audio capture: backend stream error, recreating"); @@ -458,6 +471,7 @@ fn run_capture( pub fn spawn_audio_playback( cfg: &AudioConfig, rx: mpsc::Receiver, + shutdown_rx: watch::Receiver, ) -> std::thread::JoinHandle<()> { let sample_rate = cfg.sample_rate; let channels = cfg.channels as u16; @@ -465,7 +479,9 @@ pub fn spawn_audio_playback( let device_name = cfg.device.clone(); std::thread::spawn(move || { - if let Err(e) = run_playback(sample_rate, channels, frame_duration_ms, device_name, rx) { + if let Err(e) = + run_playback(sample_rate, channels, frame_duration_ms, device_name, rx, shutdown_rx) + { error!("Audio playback thread error: {}", e); } }) @@ -477,6 +493,7 @@ fn run_playback( frame_duration_ms: u16, device_name: Option, mut rx: mpsc::Receiver, + shutdown_rx: watch::Receiver, ) -> Result<(), Box> { use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use std::sync::mpsc::TryRecvError as StdTryRecvError; @@ -513,6 +530,11 @@ fn run_playback( let mut channel_closed = false; loop { + if *shutdown_rx.borrow() { + info!("Audio playback: shutdown signal received, exiting"); + return Ok(()); + } + // Re-enumerate the device on every recovery cycle: after POLLERR the // existing device handle can be stale (especially for USB audio). let host = cpal::default_host(); @@ -600,6 +622,11 @@ fn run_playback( } loop { + if *shutdown_rx.borrow() { + info!("Audio playback: shutdown signal received, exiting"); + return Ok(()); + } + match stream_err_rx.try_recv() { Ok(()) | Err(StdTryRecvError::Disconnected) => { warn!("Audio playback: backend stream error, recreating"); diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index 3d94624..7bb89fa 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -474,6 +474,7 @@ fn spawn_rig_audio_stack( &rig_cfg.audio, rx_audio_tx.clone(), Some(pcm_tx.clone()), + shutdown_rx.clone(), ); } @@ -542,7 +543,8 @@ fn spawn_rig_audio_stack( } if rig_cfg.audio.tx_enabled { - let _playback_thread = audio::spawn_audio_playback(&rig_cfg.audio, tx_audio_rx); + let _playback_thread = + audio::spawn_audio_playback(&rig_cfg.audio, tx_audio_rx, shutdown_rx.clone()); } let audio_shutdown_rx = shutdown_rx.clone();