From 999d2c0436e8d9ee9934b17ee849c884d9e1d9f2 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Thu, 26 Mar 2026 21:20:15 +0100 Subject: [PATCH] [fix](trx-server): prevent capacity overflow panic in audio history replay Use saturating CAS loop in adjust_total_count to prevent AtomicUsize underflow, and cap history estimate at 500k entries. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Stan Grams --- src/trx-server/src/audio.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/trx-server/src/audio.rs b/src/trx-server/src/audio.rs index 48665e6..c835c3d 100644 --- a/src/trx-server/src/audio.rs +++ b/src/trx-server/src/audio.rs @@ -229,13 +229,29 @@ impl DecoderHistories { } /// Adjust the atomic total count after a record/prune/clear operation. + /// + /// Uses a CAS loop for decrements to prevent underflow wrapping the + /// counter to `usize::MAX` (which would cause a capacity-overflow panic + /// when pre-allocating the history replay blob). fn adjust_total_count(&self, old_len: usize, new_len: usize) { if new_len > old_len { self.total_count .fetch_add(new_len - old_len, Ordering::Relaxed); } else if old_len > new_len { - self.total_count - .fetch_sub(old_len - new_len, Ordering::Relaxed); + let delta = old_len - new_len; + let mut current = self.total_count.load(Ordering::Relaxed); + loop { + let next = current.saturating_sub(delta); + match self.total_count.compare_exchange_weak( + current, + next, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(actual) => current = actual, + } + } } } @@ -2872,7 +2888,7 @@ async fn handle_audio_client( let history_blob = { // Estimate ~256 bytes per message; pre-allocate to avoid repeated // reallocation for large histories. - let estimated_msgs = histories.estimated_total_count(); + let estimated_msgs = histories.estimated_total_count().min(500_000); let mut blob: Vec = Vec::with_capacity(estimated_msgs.saturating_mul(256)); let mut count = 0usize;