Skip to main content

commonware_utils/channel/
tracked.rs

1//! A channel that tracks message delivery.
2//!
3//! This channel provides message delivery tracking. Each sent message includes
4//! a [Guard] that tracks when the message has been fully processed. When ALL
5//! references to the guard are dropped, the message is marked as delivered.
6//
7//! # Features
8//!
9//! - **Watermarks**: Get the highest sequence number where all messages up to it have been delivered
10//! - **Batches**: Assign batches to messages and track pending counts per batch
11//! - **Clonable Guards**: Guards can be cloned and shared; delivery happens when all clones are dropped
12//
13//! # Sequence Number Overflow
14//!
15//! Uses [u64] for sequence numbers. At 100 messages per nanosecond, overflow occurs after ~5.85 years.
16//! Systems requiring more message throughput should implement periodic resets or use external sequence management.
17//
18//! # Example
19//!
20//! ```
21//! use futures::executor::block_on;
22//! use commonware_utils::channel::tracked;
23//! block_on(async {
24//!     let (sender, mut receiver) = tracked::bounded::<String, u64>(10);
25//
26//!     // Send a message with batch ID
27//!     let sequence = sender.send(Some(1), "hello".into()).await.unwrap();
28//
29//!     // Check pending messages
30//!     assert_eq!(sender.pending(1), 1);
31//!     assert_eq!(sender.watermark(), 0);
32//
33//!     // Receive and process
34//!     let msg = receiver.recv().await.unwrap();
35//!     assert_eq!(msg.data, "hello");
36//
37//!     // Delivery waits until all clones are dropped
38//!     let guard_clone = msg.guard.clone();
39//!     drop(msg.guard);
40//!     assert_eq!(sender.watermark(), 0); // Still not delivered
41//
42//!     // Drop the last guard reference to mark as delivered
43//!     drop(guard_clone);
44//!     assert_eq!(sender.pending(1), 0);
45//!     assert_eq!(sender.watermark(), 1);
46//! });
47//! ```
48
49use super::mpsc::{
50    self,
51    error::{SendError, TryRecvError, TrySendError},
52};
53use crate::{Widen, sync::Mutex};
54use futures::Stream;
55use std::{
56    collections::{HashMap, VecDeque},
57    hash::Hash,
58    pin::Pin,
59    sync::Arc,
60    task::{Context, Poll},
61};
62
63/// A guard that tracks message delivery. When all clones are dropped, the message is marked as
64/// delivered.
65#[derive(Clone)]
66pub struct Guard<B: Eq + Hash> {
67    inner: Arc<Inner<B>>,
68}
69
70/// Delivery bookkeeping shared by the clones of one [Guard].
71struct Inner<B: Eq + Hash> {
72    sequence: u64,
73    tracker: Arc<Mutex<State<B>>>,
74
75    batch: Option<B>,
76}
77
78impl<B: Eq + Hash> Drop for Inner<B> {
79    fn drop(&mut self) {
80        // Get the state
81        let mut state = self.tracker.lock();
82
83        // Mark the message as delivered
84        let index = usize::try_from(self.sequence - state.watermark - 1)
85            .expect("pending index must fit in usize");
86        state.pending[index] = true;
87
88        // Advance the watermark past the delivered prefix
89        while state.pending.pop_front_if(|delivered| *delivered).is_some() {
90            state.watermark += 1;
91        }
92
93        // Update the batch count (if necessary). `remove` drops the map's copy of the batch while
94        // the lock is held, but the guard's own batch is dropped with the rest of `Inner` after
95        // `state` is released. A batch destructor that runs then (for example, when the guard
96        // holds the last `Arc` reference) may lock the tracker.
97        if let Some(batch) = &self.batch {
98            let count = state.batches.get_mut(batch).expect("batch must be tracked");
99            if *count > 1 {
100                *count -= 1;
101            } else {
102                state.batches.remove(batch);
103            }
104        }
105    }
106}
107
108/// A message containing data and a [Guard] that tracks delivery.
109pub struct Message<T, B: Eq + Hash> {
110    /// The data of the message.
111    pub data: T,
112    /// The [Guard] that tracks delivery.
113    ///
114    /// When all clones of the guard are dropped, the message is considered delivered.
115    pub guard: Guard<B>,
116}
117
118/// The state of the [Tracker].
119struct State<B> {
120    /// Every sequence up to and including the watermark has been delivered.
121    watermark: u64,
122    /// The number of undelivered messages in each batch.
123    batches: HashMap<B, usize>,
124    /// `pending[i]` records whether sequence `watermark + 1 + i` has been delivered. Every
125    /// sequence issued above the watermark has a slot, so [Tracker::guard] issues
126    /// `watermark + 1 + pending.len()` next.
127    pending: VecDeque<bool>,
128}
129
130impl<B> Default for State<B> {
131    fn default() -> Self {
132        Self {
133            watermark: 0,
134            batches: HashMap::new(),
135            pending: VecDeque::new(),
136        }
137    }
138}
139
140/// Tracks delivery state across all messages.
141///
142/// Note on sequence overflow: Using u64 for sequence numbers provides ample headroom.
143/// At 100 messages per nanosecond, it would take ~5.85 years to overflow.
144/// For systems requiring longer uptime without restart, consider implementing
145/// sequence number wrapping with careful watermark handling.
146#[derive(Clone)]
147struct Tracker<B: Eq + Hash + Clone> {
148    state: Arc<Mutex<State<B>>>,
149}
150
151impl<B: Eq + Hash + Clone> Tracker<B> {
152    fn new() -> Self {
153        Self {
154            state: Arc::new(Mutex::new(State::default())),
155        }
156    }
157
158    fn guard(&self, batch: Option<B>) -> Guard<B> {
159        // Get state
160        let mut state = self.state.lock();
161
162        // Issue the next sequence and track it as not yet delivered
163        let issued: u64 = Widen::widen(state.pending.len());
164        let sequence = state.watermark + 1 + issued;
165        state.pending.push_back(false);
166
167        // Update batch count if provided
168        if let Some(batch) = &batch {
169            *state.batches.entry(batch.clone()).or_insert(0) += 1;
170        }
171
172        Guard {
173            inner: Arc::new(Inner {
174                sequence,
175                tracker: self.state.clone(),
176
177                batch,
178            }),
179        }
180    }
181}
182
183/// A sender that wraps `Sender` and tracks message delivery.
184#[derive(Clone)]
185pub struct Sender<T, B: Eq + Hash + Clone> {
186    inner: mpsc::Sender<Message<T, B>>,
187    tracker: Tracker<B>,
188}
189
190impl<T, B: Eq + Hash + Clone> Sender<T, B> {
191    /// Sends a message with an optional batch ID and returns a delivery guard.
192    pub async fn send(&self, batch: Option<B>, data: T) -> Result<u64, SendError<Message<T, B>>> {
193        // Create the guard
194        let guard = self.tracker.guard(batch);
195        let watermark = guard.inner.sequence;
196
197        // Send the message
198        let msg = Message { data, guard };
199        self.inner.send(msg).await?;
200
201        Ok(watermark)
202    }
203
204    /// Tries to send a message without blocking.
205    pub fn try_send(&self, batch: Option<B>, data: T) -> Result<u64, TrySendError<Message<T, B>>> {
206        // Create the guard
207        let guard = self.tracker.guard(batch);
208        let watermark = guard.inner.sequence;
209
210        // Send the message
211        let msg = Message { data, guard };
212        self.inner.try_send(msg)?;
213
214        Ok(watermark)
215    }
216
217    /// Returns the current delivery watermark (highest sequence number where all messages up to and including it have been delivered).
218    pub fn watermark(&self) -> u64 {
219        self.tracker.state.lock().watermark
220    }
221
222    /// Returns the number of pending messages for a specific batch.
223    pub fn pending(&self, batch: B) -> usize {
224        self.tracker
225            .state
226            .lock()
227            .batches
228            .get(&batch)
229            .copied()
230            .unwrap_or(0)
231    }
232}
233
234/// A receiver that wraps [mpsc::Receiver] and provides tracked messages.
235pub struct Receiver<T, B: Eq + Hash> {
236    inner: mpsc::Receiver<Message<T, B>>,
237}
238
239impl<T, B: Eq + Hash> Receiver<T, B> {
240    /// Receives the next message.
241    pub async fn recv(&mut self) -> Option<Message<T, B>> {
242        self.inner.recv().await
243    }
244
245    /// Tries to receive a message without blocking.
246    pub fn try_recv(&mut self) -> Result<Message<T, B>, TryRecvError> {
247        self.inner.try_recv()
248    }
249}
250
251impl<T, B: Eq + Hash> Stream for Receiver<T, B> {
252    type Item = Message<T, B>;
253
254    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
255        self.inner.poll_recv(cx)
256    }
257}
258
259/// Create a new bounded channel with delivery tracking.
260pub fn bounded<T, B: Eq + Hash + Clone>(buffer: usize) -> (Sender<T, B>, Receiver<T, B>) {
261    let (tx, rx) = mpsc::channel(buffer);
262    let sender = Sender {
263        inner: tx,
264        tracker: Tracker::new(),
265    };
266    let receiver = Receiver { inner: rx };
267    (sender, receiver)
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use futures::executor::block_on;
274
275    /// A batch whose destructor runs a callback.
276    struct CallbackBatch {
277        id: u64,
278        on_drop: Box<dyn Fn() + Send + Sync>,
279    }
280
281    impl PartialEq for CallbackBatch {
282        fn eq(&self, other: &Self) -> bool {
283            self.id == other.id
284        }
285    }
286
287    impl Eq for CallbackBatch {}
288
289    impl Hash for CallbackBatch {
290        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
291            self.id.hash(state);
292        }
293    }
294
295    impl Drop for CallbackBatch {
296        fn drop(&mut self) {
297            (self.on_drop)();
298        }
299    }
300
301    #[test]
302    fn test_batch_destructor_can_read_watermark() {
303        // Run the channel on a worker thread so the receive timeout below fails the test if the
304        // batch destructor deadlocks on the tracker.
305        let (observed, observations) = std::sync::mpsc::channel();
306        let worker = std::thread::spawn(move || {
307            block_on(async move {
308                let (sender, mut receiver) = bounded::<(), Arc<CallbackBatch>>(1);
309                let observer = sender.clone();
310                let batch = Arc::new(CallbackBatch {
311                    id: 1,
312                    on_drop: Box::new(move || observed.send(observer.watermark()).unwrap()),
313                });
314                sender.send(Some(batch), ()).await.unwrap();
315
316                // Dropping the only guard drops the last reference to the batch
317                drop(receiver.recv().await.unwrap());
318            });
319        });
320        let watermark = observations
321            .recv_timeout(std::time::Duration::from_secs(5))
322            .expect("batch destructor must read the watermark");
323        assert_eq!(watermark, 1);
324        worker.join().unwrap();
325    }
326
327    #[test]
328    fn test_basic() {
329        block_on(async move {
330            let (sender, mut receiver) = bounded::<i32, u64>(10);
331
332            // Send a message without batch ID
333            let watermark = sender.send(None, 42).await.unwrap();
334            assert_eq!(watermark, 1);
335            assert_eq!(sender.watermark(), 0);
336
337            // Receive the message but don't drop the guard yet
338            let msg = receiver.recv().await.unwrap();
339            assert_eq!(msg.data, 42);
340            assert_eq!(sender.watermark(), 0);
341
342            // Drop the guard to mark as delivered
343            drop(msg.guard);
344            assert_eq!(sender.watermark(), 1);
345        });
346    }
347
348    #[test]
349    fn test_out_of_order_delivery() {
350        block_on(async move {
351            let (sender, mut receiver) = bounded::<i32, u64>(4);
352            for value in 1..=4 {
353                sender.send(Some(1), value).await.unwrap();
354            }
355            let first = receiver.recv().await.unwrap();
356            let second = receiver.recv().await.unwrap();
357            let third = receiver.recv().await.unwrap();
358            let fourth = receiver.recv().await.unwrap();
359
360            // Delivering the second and fourth messages leaves the first undelivered, so the
361            // watermark stays at zero
362            drop(second);
363            drop(fourth);
364            assert_eq!(sender.watermark(), 0);
365            assert_eq!(sender.pending(1), 2);
366
367            // A message sent while delivered messages wait behind the undelivered first takes the
368            // sequence after every issued message, delivered or not
369            let fifth = sender.send(Some(2), 5).await.unwrap();
370            assert_eq!(fifth, 5);
371            assert_eq!(sender.pending(2), 1);
372
373            // Delivering the first advances the watermark past the already delivered second
374            drop(first);
375            assert_eq!(sender.watermark(), 2);
376            assert_eq!(sender.pending(1), 1);
377
378            // Delivering the third advances the watermark past the already delivered fourth
379            drop(third);
380            assert_eq!(sender.watermark(), 4);
381            assert_eq!(sender.pending(1), 0);
382
383            // A message sent after the watermark advanced continues from the watermark
384            let sixth = sender.send(Some(2), 6).await.unwrap();
385            assert_eq!(sixth, 6);
386            assert_eq!(sender.pending(2), 2);
387
388            // Delivering the fifth and sixth in order advances the watermark past both
389            drop(receiver.recv().await.unwrap());
390            drop(receiver.recv().await.unwrap());
391            assert_eq!(sender.watermark(), 6);
392            assert_eq!(sender.pending(2), 0);
393        });
394    }
395
396    #[test]
397    fn test_batch_tracking() {
398        block_on(async move {
399            let (sender, mut receiver) = bounded::<String, u64>(10);
400
401            // Send messages with different batch IDs
402            let watermark1 = sender.send(Some(100), "msg1".into()).await.unwrap();
403            let watermark2 = sender.send(Some(100), "msg2".into()).await.unwrap();
404            let watermark3 = sender.send(Some(200), "msg3".into()).await.unwrap();
405
406            assert_eq!(watermark1, 1);
407            assert_eq!(watermark2, 2);
408            assert_eq!(watermark3, 3);
409            assert_eq!(sender.pending(100), 2);
410            assert_eq!(sender.pending(200), 1);
411            assert_eq!(sender.pending(300), 0);
412
413            // Receive and process first message
414            let msg1 = receiver.recv().await.unwrap();
415            assert_eq!(msg1.data, "msg1");
416            drop(msg1.guard);
417
418            assert_eq!(sender.pending(100), 1);
419            assert_eq!(sender.pending(200), 1);
420
421            // Receive and process remaining messages
422            let msg2 = receiver.recv().await.unwrap();
423            let msg3 = receiver.recv().await.unwrap();
424            drop(msg2.guard);
425            drop(msg3.guard);
426
427            assert_eq!(sender.pending(100), 0);
428            assert_eq!(sender.pending(200), 0);
429        });
430    }
431
432    #[test]
433    fn test_cloned_guards() {
434        block_on(async move {
435            let (sender, mut receiver) = bounded::<&str, u64>(10);
436
437            let watermark = sender.send(Some(1), "test").await.unwrap();
438            assert_eq!(watermark, 1);
439
440            // Receive the message immediately
441            let msg = receiver.recv().await.unwrap();
442            assert_eq!(msg.data, "test");
443
444            // Clones of one guard share a single delivery
445            let msg_guard_clone1: Guard<u64> = msg.guard.clone();
446            let msg_guard_clone2: Guard<u64> = msg.guard.clone();
447
448            assert_eq!(sender.pending(1), 1);
449            assert_eq!(sender.watermark(), 0);
450
451            // Drop original and one clone
452            drop(msg.guard);
453            drop(msg_guard_clone1);
454            assert_eq!(sender.pending(1), 1);
455            assert_eq!(sender.watermark(), 0);
456
457            // Drop last clone
458            drop(msg_guard_clone2);
459            assert_eq!(sender.pending(1), 0);
460            assert_eq!(sender.watermark(), 1);
461        });
462    }
463
464    #[test]
465    fn test_try_send() {
466        block_on(async move {
467            let (sender, mut receiver) = bounded::<i32, u64>(2);
468
469            // Try send should work when buffer has space
470            let watermark1 = sender.try_send(Some(10), 1).unwrap();
471            let watermark2 = sender.try_send(Some(10), 2).unwrap();
472
473            assert_eq!(sender.pending(10), 2);
474            assert_eq!(watermark1, 1);
475            assert_eq!(watermark2, 2);
476
477            // Receive messages
478            let msg1 = receiver.recv().await.unwrap();
479            assert_eq!(msg1.data, 1);
480            drop(msg1.guard);
481
482            assert_eq!(sender.pending(10), 1);
483
484            let msg2 = receiver.recv().await.unwrap();
485            drop(msg2.guard);
486
487            assert_eq!(sender.pending(10), 0);
488        });
489    }
490
491    #[test]
492    fn test_channel_closure() {
493        block_on(async move {
494            let (sender, receiver) = bounded::<i32, u64>(10);
495
496            let _guard = sender.send(None, 1).await.unwrap();
497
498            // Drop receiver
499            drop(receiver);
500
501            // Next send should fail
502            assert!(sender.send(None, 2).await.is_err());
503        });
504    }
505}