Skip to main content

pamoja_bus/
lib.rs

1//! An in-memory typed publish/subscribe event bus.
2//!
3//! [`BroadcastBus`] implements the core [`EventBus`] trait
4//! over a bounded broadcast channel: every event published is delivered to every
5//! current subscriber. Producers such as sensors and transports publish events,
6//! and consumers await them, all statically typed to one event type per bus.
7//!
8//! The bus is bounded, so a subscriber that falls far enough behind drops the
9//! events it missed and resumes from the most recent ones. This keeps a slow
10//! consumer from holding memory without bound, which matters on constrained
11//! devices.
12
13use pamoja_core::{EventBus, Result};
14use tokio::sync::broadcast;
15
16/// A typed publish/subscribe bus that broadcasts each event to all subscribers.
17///
18/// Every handle can both publish and receive. Use [`subscribe`](BroadcastBus::subscribe)
19/// to add an independent consumer; an event published after a handle subscribes
20/// is delivered to it. A subscriber only sees events published after it
21/// subscribed, mirroring a live pub/sub channel.
22///
23/// # Examples
24///
25/// ```
26/// use pamoja_core::EventBus;
27/// use pamoja_bus::BroadcastBus;
28///
29/// # async fn run() -> pamoja_core::Result<()> {
30/// let bus = BroadcastBus::new(16);
31/// let mut subscriber = bus.subscribe();
32/// bus.publish("reading").await?;
33/// assert_eq!(subscriber.next_event().await?, Some("reading"));
34/// # Ok(())
35/// # }
36/// ```
37pub struct BroadcastBus<E> {
38    sender: broadcast::Sender<E>,
39    receiver: broadcast::Receiver<E>,
40}
41
42impl<E: Clone> BroadcastBus<E> {
43    /// Creates a bus buffering up to `capacity` unread events per subscriber.
44    ///
45    /// # Arguments
46    ///
47    /// * `capacity` - the per-subscriber buffer depth; a subscriber further behind
48    ///   than this drops the events it missed. Values below one are raised to one.
49    ///
50    /// # Returns
51    ///
52    /// A bus with one handle that can publish and receive.
53    pub fn new(capacity: usize) -> Self {
54        let (sender, receiver) = broadcast::channel(capacity.max(1));
55        Self { sender, receiver }
56    }
57
58    /// Creates another handle to the same bus with its own independent subscription.
59    ///
60    /// # Returns
61    ///
62    /// A handle that receives events published after this call and can also publish.
63    pub fn subscribe(&self) -> Self {
64        Self {
65            sender: self.sender.clone(),
66            receiver: self.sender.subscribe(),
67        }
68    }
69}
70
71impl<E: Clone> EventBus for BroadcastBus<E> {
72    type Event = E;
73
74    async fn publish(&self, event: Self::Event) -> Result<()> {
75        // `send` errors only when there are no receivers; this handle holds its
76        // own, so a publish always succeeds.
77        let _ = self.sender.send(event);
78        Ok(())
79    }
80
81    async fn next_event(&mut self) -> Result<Option<Self::Event>> {
82        loop {
83            match self.receiver.recv().await {
84                Ok(event) => return Ok(Some(event)),
85                Err(broadcast::error::RecvError::Closed) => return Ok(None),
86                // The subscriber fell behind; skip the dropped events and resume.
87                Err(broadcast::error::RecvError::Lagged(_)) => continue,
88            }
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[tokio::test]
98    async fn delivers_a_published_event() {
99        let mut bus = BroadcastBus::new(8);
100        bus.publish(1).await.expect("publish");
101        assert_eq!(bus.next_event().await.expect("next"), Some(1));
102    }
103
104    #[tokio::test]
105    async fn fans_out_to_every_subscriber() {
106        let bus = BroadcastBus::new(8);
107        let mut first = bus.subscribe();
108        let mut second = bus.subscribe();
109
110        bus.publish("event").await.expect("publish");
111
112        assert_eq!(first.next_event().await.expect("next"), Some("event"));
113        assert_eq!(second.next_event().await.expect("next"), Some("event"));
114    }
115
116    #[tokio::test]
117    async fn a_lagging_subscriber_skips_dropped_events_and_resumes() {
118        let bus = BroadcastBus::new(2);
119        let mut subscriber = bus.subscribe();
120
121        for value in 0..5 {
122            bus.publish(value).await.expect("publish");
123        }
124
125        // Capacity is two, so the three oldest events are dropped; the subscriber
126        // resumes with the two most recent, in order.
127        let mut seen = Vec::new();
128        seen.push(subscriber.next_event().await.expect("next").expect("event"));
129        seen.push(subscriber.next_event().await.expect("next").expect("event"));
130        assert_eq!(seen, vec![3, 4]);
131    }
132}