From 74baa63f50f27d4b87ad9009149594f9faba9287 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Fri, 5 Dec 2025 20:27:55 -0700 Subject: [PATCH] simplify code that locks log buffer for reading --- signal-gateway/src/circular_buffer.rs | 3 ++- signal-gateway/src/gateway/log_buffer.rs | 23 ++++++++++++----------- signal-gateway/src/gateway/log_handler.rs | 9 +++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/signal-gateway/src/circular_buffer.rs b/signal-gateway/src/circular_buffer.rs index c405787..4c630aa 100644 --- a/signal-gateway/src/circular_buffer.rs +++ b/signal-gateway/src/circular_buffer.rs @@ -39,6 +39,7 @@ impl CircularBuffer { } /// Returns the number of elements in the buffer. + #[allow(unused)] pub fn len(&self) -> usize { self.buf.len() } @@ -56,7 +57,7 @@ impl CircularBuffer { } /// Returns an iterator over the elements, from oldest to newest. - pub fn iter(&self) -> impl DoubleEndedIterator { + pub fn iter(&self) -> impl DoubleEndedIterator + ExactSizeIterator { self.buf.iter() } diff --git a/signal-gateway/src/gateway/log_buffer.rs b/signal-gateway/src/gateway/log_buffer.rs index 61c5fe5..dd791e4 100644 --- a/signal-gateway/src/gateway/log_buffer.rs +++ b/signal-gateway/src/gateway/log_buffer.rs @@ -41,18 +41,19 @@ impl LogBuffer { buf.clear(); } - /// Iterate over all messages without modifying the buffer. + /// Access the buffer contents via an iterator. /// - /// Messages are passed to `f` in reverse order (newest first). - pub fn for_each(&self, mut f: impl FnMut(&LogMessage)) { + /// The iterator yields messages in reverse order (newest first) and + /// implements `ExactSizeIterator`, so `iter.len()` returns the count. + // NOTE: We use `&mut dyn ExactSizeIterator` rather than `impl FnOnce(impl ExactSizeIterator)` + // because Rust doesn't allow nested `impl Trait` in that position. A generic parameter + // `F: FnOnce(I) where I: ExactSizeIterator` doesn't work either because `I` would be + // caller-determined, but we need to pass our concrete iterator type. HRTB with the + // concrete type (`F: for<'a> FnOnce(Rev>)`) works but leaks + // implementation details. + pub fn with_iter(&self, f: impl FnOnce(&mut dyn ExactSizeIterator) -> R) -> R { let buf = self.buf.lock().unwrap(); - for log_msg in buf.iter().rev() { - f(log_msg); - } - } - - /// Returns the number of messages currently in the buffer. - pub fn len(&self) -> usize { - self.buf.lock().unwrap().len() + let mut iter = buf.iter().rev(); + f(&mut iter) } } diff --git a/signal-gateway/src/gateway/log_handler.rs b/signal-gateway/src/gateway/log_handler.rs index b4cdee6..e996cb4 100644 --- a/signal-gateway/src/gateway/log_handler.rs +++ b/signal-gateway/src/gateway/log_handler.rs @@ -129,10 +129,11 @@ impl LogHandler { use std::fmt::Write; writeln!(&mut text, "=== [{origin}] ===").unwrap(); - writeln!(&mut text, "{} log messages (newest first):", buffer.len()).unwrap(); - - buffer.for_each(|log_msg| { - self.write_log_msg(&mut text, log_msg, now); + buffer.with_iter(|iter| { + writeln!(&mut text, "{} log messages (newest first):", iter.len()).unwrap(); + for log_msg in iter { + self.write_log_msg(&mut text, log_msg, now); + } }); text.push('\n'); }