[refactor](trx-client): switch VChanAudioCmd to bounded channels (cap 256)
Replace unbounded_channel with channel(256) for VChanAudioCmd to prevent unlimited memory accumulation under backpressure. Use try_send in synchronous contexts. https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4 Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -92,11 +92,11 @@ pub async fn run_multi_rig_audio_manager(
|
||||
replay_history_sink: Option<Arc<dyn Fn(DecodedMessage) + Send + Sync>>,
|
||||
mut shutdown_rx: watch::Receiver<bool>,
|
||||
vchan_audio: Arc<RwLock<HashMap<Uuid, broadcast::Sender<Bytes>>>>,
|
||||
_vchan_cmd_rx: mpsc::UnboundedReceiver<VChanAudioCmd>,
|
||||
_vchan_cmd_rx: mpsc::Receiver<VChanAudioCmd>,
|
||||
vchan_destroyed_tx: Option<broadcast::Sender<Uuid>>,
|
||||
rig_audio_rx: Arc<RwLock<HashMap<String, broadcast::Sender<Bytes>>>>,
|
||||
rig_audio_info: Arc<RwLock<HashMap<String, watch::Sender<Option<AudioStreamInfo>>>>>,
|
||||
rig_vchan_audio_cmd: Arc<RwLock<HashMap<String, mpsc::UnboundedSender<VChanAudioCmd>>>>,
|
||||
rig_vchan_audio_cmd: Arc<RwLock<HashMap<String, mpsc::Sender<VChanAudioCmd>>>>,
|
||||
) {
|
||||
// TX frames from the microphone go to the selected rig only.
|
||||
// We wrap the single tx_rx receiver so the per-rig task for the selected
|
||||
@@ -168,9 +168,10 @@ pub async fn run_multi_rig_audio_manager(
|
||||
.clone()
|
||||
};
|
||||
|
||||
// Create per-rig vchan cmd channel.
|
||||
// Create per-rig vchan cmd channel (bounded to prevent
|
||||
// unbounded memory growth under backpressure).
|
||||
let (per_rig_vchan_tx, per_rig_vchan_rx) =
|
||||
mpsc::unbounded_channel::<VChanAudioCmd>();
|
||||
mpsc::channel::<VChanAudioCmd>(256);
|
||||
if let Ok(mut map) = rig_vchan_audio_cmd.write() {
|
||||
map.insert(rig_id.clone(), per_rig_vchan_tx);
|
||||
}
|
||||
@@ -265,7 +266,7 @@ async fn run_single_rig_audio_client(
|
||||
replay_history_sink: Option<Arc<dyn Fn(DecodedMessage) + Send + Sync>>,
|
||||
mut shutdown_rx: watch::Receiver<bool>,
|
||||
vchan_audio: Arc<RwLock<HashMap<Uuid, broadcast::Sender<Bytes>>>>,
|
||||
mut vchan_cmd_rx: mpsc::UnboundedReceiver<VChanAudioCmd>,
|
||||
mut vchan_cmd_rx: mpsc::Receiver<VChanAudioCmd>,
|
||||
vchan_destroyed_tx: Option<broadcast::Sender<Uuid>>,
|
||||
) {
|
||||
let mut reconnect_delay = Duration::from_secs(1);
|
||||
@@ -409,7 +410,7 @@ async fn handle_single_rig_connection(
|
||||
replay_history_sink: Option<Arc<dyn Fn(DecodedMessage) + Send + Sync>>,
|
||||
shutdown_rx: &mut watch::Receiver<bool>,
|
||||
vchan_audio: &Arc<RwLock<HashMap<Uuid, broadcast::Sender<Bytes>>>>,
|
||||
vchan_cmd_rx: &mut mpsc::UnboundedReceiver<VChanAudioCmd>,
|
||||
vchan_cmd_rx: &mut mpsc::Receiver<VChanAudioCmd>,
|
||||
active_subs: &mut HashMap<Uuid, ActiveVChanSub>,
|
||||
vchan_destroyed_tx: &Option<broadcast::Sender<Uuid>>,
|
||||
) -> std::io::Result<()> {
|
||||
|
||||
@@ -454,7 +454,7 @@ async fn async_init() -> DynResult<AppState> {
|
||||
frontend_runtime.decode_rx = Some(decode_tx.clone());
|
||||
|
||||
// Virtual-channel audio: shared broadcaster map + command channel.
|
||||
let (vchan_cmd_tx, vchan_cmd_rx) = mpsc::unbounded_channel::<trx_frontend::VChanAudioCmd>();
|
||||
let (vchan_cmd_tx, vchan_cmd_rx) = mpsc::channel::<trx_frontend::VChanAudioCmd>(256);
|
||||
*frontend_runtime.vchan_audio_cmd.lock().unwrap() = Some(vchan_cmd_tx);
|
||||
|
||||
let (vchan_destroyed_tx, _) = broadcast::channel::<uuid::Uuid>(64);
|
||||
|
||||
@@ -254,7 +254,7 @@ impl BackgroundDecodeManager {
|
||||
if let Some(rig_id) = self.active_rig_id() {
|
||||
if let Ok(map) = self.context.rig_vchan_audio_cmd.read() {
|
||||
if let Some(tx) = map.get(&rig_id) {
|
||||
let _ = tx.send(cmd);
|
||||
let _ = tx.try_send(cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,7 @@ impl BackgroundDecodeManager {
|
||||
// Fall back to global sender.
|
||||
if let Ok(guard) = self.context.vchan_audio_cmd.lock() {
|
||||
if let Some(tx) = guard.as_ref() {
|
||||
let _ = tx.send(cmd);
|
||||
let _ = tx.try_send(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,16 +108,16 @@ pub struct ClientChannelManager {
|
||||
pub change_tx: broadcast::Sender<String>,
|
||||
pub max_channels: usize,
|
||||
/// Global fallback sender to the audio-client task for virtual-channel audio commands.
|
||||
pub audio_cmd: std::sync::Mutex<Option<mpsc::UnboundedSender<VChanAudioCmd>>>,
|
||||
pub audio_cmd: std::sync::Mutex<Option<mpsc::Sender<VChanAudioCmd>>>,
|
||||
/// Per-rig vchan command senders. Commands are routed to the per-rig sender
|
||||
/// when available, falling back to the global `audio_cmd`.
|
||||
pub rig_vchan_audio_cmd: Arc<RwLock<HashMap<String, mpsc::UnboundedSender<VChanAudioCmd>>>>,
|
||||
pub rig_vchan_audio_cmd: Arc<RwLock<HashMap<String, mpsc::Sender<VChanAudioCmd>>>>,
|
||||
}
|
||||
|
||||
impl ClientChannelManager {
|
||||
pub fn new(
|
||||
max_channels: usize,
|
||||
rig_vchan_audio_cmd: Arc<RwLock<HashMap<String, mpsc::UnboundedSender<VChanAudioCmd>>>>,
|
||||
rig_vchan_audio_cmd: Arc<RwLock<HashMap<String, mpsc::Sender<VChanAudioCmd>>>>,
|
||||
) -> Self {
|
||||
let (change_tx, _) = broadcast::channel(64);
|
||||
Self {
|
||||
@@ -131,7 +131,7 @@ impl ClientChannelManager {
|
||||
}
|
||||
|
||||
/// Wire the global audio-command sender as fallback.
|
||||
pub fn set_audio_cmd(&self, tx: mpsc::UnboundedSender<VChanAudioCmd>) {
|
||||
pub fn set_audio_cmd(&self, tx: mpsc::Sender<VChanAudioCmd>) {
|
||||
*self.audio_cmd.lock().unwrap() = Some(tx);
|
||||
}
|
||||
|
||||
@@ -141,13 +141,13 @@ impl ClientChannelManager {
|
||||
// Try per-rig sender first.
|
||||
if let Ok(map) = self.rig_vchan_audio_cmd.read() {
|
||||
if let Some(tx) = map.get(rig_id) {
|
||||
let _ = tx.send(cmd);
|
||||
let _ = tx.try_send(cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fall back to global sender.
|
||||
if let Some(tx) = self.audio_cmd.lock().unwrap().as_ref() {
|
||||
let _ = tx.send(cmd);
|
||||
let _ = tx.try_send(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user