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//! [`EventPublisher`] is the handle for a part that only announces. It has no
9//! queue of its own, it clones into every task and callback that publishes, and
10//! publishing from it never waits, so it can announce while a subscriber on the
11//! same bus is waiting for its next event.
12//!
13//! The bus is bounded, so a subscriber that falls far enough behind drops the
14//! events it missed and resumes from the most recent ones, and
15//! [`BroadcastBus::missed`] counts what it lost. This keeps a slow consumer from
16//! holding memory without bound, which matters on constrained devices.
17//!
18//! # Examples
19//!
20//! A pump controller's events reach a logger and a dashboard: each subscriber hears every
21//! event published after it joined, in order.
22//!
23//! ```
24//! use pamoja_bus::BroadcastBus;
25//! use pamoja_core::EventBus;
26//!
27//! # tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(async {
28//! let bus = BroadcastBus::new(16);
29//! let mut logger = bus.subscribe();
30//! let mut dashboard = bus.subscribe();
31//!
32//! bus.publish("pump started").await?;
33//! bus.publish("pressure low").await?;
34//! assert_eq!(logger.next_event().await?, Some("pump started"));
35//! assert_eq!(logger.next_event().await?, Some("pressure low"));
36//! assert_eq!(dashboard.next_event().await?, Some("pump started"));
37//! # Ok::<(), pamoja_core::Error>(())
38//! # }).unwrap();
39//! ```
40
41use pamoja_core::{EventBus, Result};
42use tokio::sync::broadcast;
43
44/// The largest buffer a bus sets aside; a larger capacity is lowered to it.
45///
46/// The buffer is allocated in full when the bus is made, so a mistaken capacity
47/// would otherwise cost its memory at once, or fail the allocation outright.
48pub const MAX_CAPACITY: usize = 1 << 20;
49
50/// Brings a requested capacity into the range a bus accepts.
51fn bounded(capacity: usize) -> usize {
52    capacity.clamp(1, MAX_CAPACITY)
53}
54
55/// A typed publish/subscribe bus that broadcasts each event to all subscribers.
56///
57/// Every handle can both publish and receive, and it receives what it publishes
58/// itself. Use [`subscribe`](BroadcastBus::subscribe) to add an independent
59/// consumer; an event published after a handle subscribes is delivered to it. A
60/// subscriber only sees events published after it subscribed, mirroring a live
61/// pub/sub channel.
62///
63/// Each handle holds a publisher of its own, so the bus stays open while any
64/// handle exists and [`next_event`](EventBus::next_event) on a `BroadcastBus`
65/// never returns `None`: it waits until an event arrives. A wait given up, by a
66/// timeout or a `select!`, takes no event.
67///
68/// # Examples
69///
70/// ```
71/// use pamoja_core::EventBus;
72/// use pamoja_bus::BroadcastBus;
73///
74/// # async fn run() -> pamoja_core::Result<()> {
75/// let bus = BroadcastBus::new(16);
76/// let mut subscriber = bus.subscribe();
77/// bus.publish("reading").await?;
78/// assert_eq!(subscriber.next_event().await?, Some("reading"));
79/// # Ok(())
80/// # }
81/// ```
82pub struct BroadcastBus<E> {
83    sender: broadcast::Sender<E>,
84    receiver: broadcast::Receiver<E>,
85    missed: u64,
86}
87
88impl<E: Clone> BroadcastBus<E> {
89    /// Creates a bus buffering unread events for each subscriber.
90    ///
91    /// # Arguments
92    ///
93    /// * `capacity` - the per-subscriber buffer depth, rounded up to the next
94    ///   power of two; a subscriber further behind than the buffer holds drops
95    ///   the events it missed. Values below one are raised to one, and values
96    ///   above [`MAX_CAPACITY`] are lowered to it.
97    ///
98    /// # Returns
99    ///
100    /// A bus with one handle that can publish and receive.
101    pub fn new(capacity: usize) -> Self {
102        let (sender, receiver) = broadcast::channel(bounded(capacity));
103        Self {
104            sender,
105            receiver,
106            missed: 0,
107        }
108    }
109
110    /// Creates another handle to the same bus with its own independent subscription.
111    ///
112    /// # Returns
113    ///
114    /// A handle that receives events published after this call and can also publish.
115    pub fn subscribe(&self) -> Self {
116        Self {
117            sender: self.sender.clone(),
118            receiver: self.sender.subscribe(),
119            missed: 0,
120        }
121    }
122
123    /// Creates a publish-only handle to the same bus.
124    ///
125    /// # Returns
126    ///
127    /// A handle that publishes without borrowing this one, so it can announce from
128    /// another task while this handle waits in [`next_event`](EventBus::next_event).
129    pub fn publisher(&self) -> EventPublisher<E> {
130        EventPublisher {
131            sender: self.sender.clone(),
132        }
133    }
134
135    /// Counts the events this handle lost by falling behind.
136    ///
137    /// # Returns
138    ///
139    /// How many events were dropped from this handle's buffer before it read
140    /// them, since it subscribed.
141    pub fn missed(&self) -> u64 {
142        self.missed
143    }
144}
145
146impl<E: Clone> EventBus for BroadcastBus<E> {
147    type Event = E;
148
149    async fn publish(&self, event: Self::Event) -> Result<()> {
150        // `send` errors only when there are no receivers; this handle holds its
151        // own, so a publish always succeeds.
152        let _ = self.sender.send(event);
153        Ok(())
154    }
155
156    async fn next_event(&mut self) -> Result<Option<Self::Event>> {
157        loop {
158            match self.receiver.recv().await {
159                Ok(event) => return Ok(Some(event)),
160                Err(broadcast::error::RecvError::Closed) => return Ok(None),
161                Err(broadcast::error::RecvError::Lagged(skipped)) => {
162                    self.missed = self.missed.saturating_add(skipped);
163                }
164            }
165        }
166    }
167}
168
169/// A publish-only handle to a bus.
170///
171/// A publisher has no buffer to fill, so a part that only announces holds one
172/// rather than a [`BroadcastBus`]. Cloning it is cheap, and publishing never
173/// waits: a subscriber that falls behind loses events rather than holding up the
174/// publisher.
175///
176/// # Examples
177///
178/// ```
179/// use pamoja_core::EventBus;
180/// use pamoja_bus::EventPublisher;
181///
182/// # async fn run() -> pamoja_core::Result<()> {
183/// let power = EventPublisher::new(8);
184/// let mut control = power.subscribe();
185/// let mut logger = power.subscribe();
186/// assert_eq!(power.publish("battery.low"), 2);
187/// assert_eq!(control.next_event().await?, Some("battery.low"));
188/// assert_eq!(logger.next_event().await?, Some("battery.low"));
189/// # Ok(())
190/// # }
191/// ```
192#[derive(Clone)]
193pub struct EventPublisher<E> {
194    sender: broadcast::Sender<E>,
195}
196
197impl<E: Clone> EventPublisher<E> {
198    /// Creates a bus with no subscribers yet, and a publisher on it.
199    ///
200    /// # Arguments
201    ///
202    /// * `capacity` - the per-subscriber buffer depth, rounded up to the next
203    ///   power of two. Values below one are raised to one, and values above
204    ///   [`MAX_CAPACITY`] are lowered to it.
205    ///
206    /// # Returns
207    ///
208    /// A publisher to take subscribers from and publish to them.
209    pub fn new(capacity: usize) -> Self {
210        let (sender, _) = broadcast::channel(bounded(capacity));
211        Self { sender }
212    }
213
214    /// Hands an event to every current subscriber.
215    ///
216    /// # Arguments
217    ///
218    /// * `event` - the event to broadcast.
219    ///
220    /// # Returns
221    ///
222    /// How many subscribers the event was handed to. An event published while no
223    /// one is subscribed is dropped, and the count is 0.
224    pub fn publish(&self, event: E) -> usize {
225        self.sender.send(event).unwrap_or(0)
226    }
227
228    /// Subscribes to the bus.
229    ///
230    /// # Returns
231    ///
232    /// A handle that receives events published after this call and can also publish.
233    pub fn subscribe(&self) -> BroadcastBus<E> {
234        BroadcastBus {
235            sender: self.sender.clone(),
236            receiver: self.sender.subscribe(),
237            missed: 0,
238        }
239    }
240
241    /// Takes another publisher on the same bus, for another part that announces.
242    ///
243    /// # Returns
244    ///
245    /// A publisher with a lifetime of its own, the same as a clone of this one.
246    pub fn publisher(&self) -> EventPublisher<E> {
247        self.clone()
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use std::time::Duration;
254
255    use super::*;
256
257    #[tokio::test]
258    async fn delivers_a_published_event() {
259        let mut bus = BroadcastBus::new(8);
260        bus.publish(1).await.expect("publish");
261        assert_eq!(bus.next_event().await.expect("next"), Some(1));
262    }
263
264    #[tokio::test]
265    async fn fans_out_to_every_subscriber() {
266        let bus = BroadcastBus::new(8);
267        let mut first = bus.subscribe();
268        let mut second = bus.subscribe();
269
270        bus.publish("event").await.expect("publish");
271
272        assert_eq!(first.next_event().await.expect("next"), Some("event"));
273        assert_eq!(second.next_event().await.expect("next"), Some("event"));
274    }
275
276    #[tokio::test]
277    async fn a_lagging_subscriber_skips_dropped_events_and_counts_them() {
278        let bus = BroadcastBus::new(2);
279        let mut subscriber = bus.subscribe();
280
281        for value in 0..5 {
282            bus.publish(value).await.expect("publish");
283        }
284
285        let mut seen = Vec::new();
286        seen.push(subscriber.next_event().await.expect("next").expect("event"));
287        seen.push(subscriber.next_event().await.expect("next").expect("event"));
288        assert_eq!(seen, vec![3, 4]);
289        assert_eq!(subscriber.missed(), 3);
290    }
291
292    #[tokio::test]
293    async fn the_capacity_is_rounded_up_to_a_power_of_two() {
294        let bus = BroadcastBus::new(3);
295        let mut subscriber = bus.subscribe();
296
297        for value in 0..5 {
298            bus.publish(value).await.expect("publish");
299        }
300
301        assert_eq!(subscriber.next_event().await.expect("next"), Some(1));
302        assert_eq!(subscriber.missed(), 1);
303    }
304
305    #[tokio::test]
306    async fn a_capacity_beyond_the_maximum_is_lowered_rather_than_allocated() {
307        let bus = BroadcastBus::new(usize::MAX);
308        let mut subscriber = bus.subscribe();
309
310        bus.publish(1u8).await.expect("publish");
311
312        assert_eq!(subscriber.next_event().await.expect("next"), Some(1));
313        assert_eq!(bounded(usize::MAX), MAX_CAPACITY);
314        assert_eq!(bounded(0), 1);
315    }
316
317    #[tokio::test]
318    async fn a_publisher_counts_the_subscribers_it_hands_an_event_to() {
319        let power = EventPublisher::new(8);
320        let mut control = power.subscribe();
321        let mut logger = power.subscribe();
322
323        assert_eq!(power.publish("battery.low"), 2);
324
325        assert_eq!(
326            control.next_event().await.expect("next"),
327            Some("battery.low")
328        );
329        assert_eq!(
330            logger.next_event().await.expect("next"),
331            Some("battery.low")
332        );
333    }
334
335    #[tokio::test]
336    async fn an_event_published_to_no_subscribers_is_dropped() {
337        let power = EventPublisher::new(8);
338        assert_eq!(power.publish("before"), 0);
339
340        let mut late = power.subscribe();
341        assert_eq!(power.publish("after"), 1);
342        assert_eq!(late.next_event().await.expect("next"), Some("after"));
343    }
344
345    #[tokio::test]
346    async fn a_publisher_announces_while_a_subscriber_waits() {
347        let mut subscriber = BroadcastBus::new(4);
348        let publisher = subscriber.publisher();
349
350        let waiting = tokio::spawn(async move { subscriber.next_event().await });
351        tokio::task::yield_now().await;
352
353        assert_eq!(publisher.publish(7), 1);
354        let event = waiting.await.expect("the waiting task").expect("next");
355        assert_eq!(event, Some(7));
356    }
357
358    #[tokio::test]
359    async fn the_bus_stays_open_while_a_subscriber_holds_it() {
360        let power: EventPublisher<u8> = EventPublisher::new(2);
361        let mut subscriber = power.subscribe();
362        drop(power);
363
364        let wait = tokio::time::timeout(Duration::from_millis(20), subscriber.next_event()).await;
365        assert!(wait.is_err(), "the wait ended with {wait:?}");
366    }
367}