use std::sync::Mutex;
use tokio::sync::broadcast;
use super::ComputerEvent;
pub struct EventBus {
tx: broadcast::Sender<ComputerEvent>,
last: Mutex<u64>,
}
impl EventBus {
pub fn new(capacity: usize) -> Self {
let (tx, _) = broadcast::channel(capacity);
Self {
tx,
last: Mutex::new(0),
}
}
pub fn subscribe(&self) -> broadcast::Receiver<ComputerEvent> {
self.tx.subscribe()
}
pub fn publish(&self, mut event: ComputerEvent) {
let mut last = self.last.lock().unwrap();
*last += 1;
event.sequence = *last;
let _ = self.tx.send(event);
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::sandbox::types::action;
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_publishes_arrive_in_sequence_order() {
let bus = Arc::new(EventBus::new(1024));
let mut rx = bus.subscribe();
let publishers: Vec<_> = (0..8)
.map(|p| {
let bus = Arc::clone(&bus);
tokio::spawn(async move {
for _ in 0..64 {
bus.publish(ComputerEvent::new(&format!("box-{p}"), action::READY));
}
})
})
.collect();
for publisher in publishers {
publisher.await.unwrap();
}
let mut expected = 1;
while let Ok(event) = rx.try_recv() {
assert_eq!(
event.sequence, expected,
"a subscriber that lost nothing sees contiguous sequences"
);
expected += 1;
}
assert_eq!(expected, 8 * 64 + 1, "every publish was received");
}
#[tokio::test]
async fn a_late_subscriber_sees_the_history_it_missed_as_a_gap() {
let bus = EventBus::new(16);
bus.publish(ComputerEvent::new("early", action::CREATED));
bus.publish(ComputerEvent::new("early", action::READY));
let mut rx = bus.subscribe();
bus.publish(ComputerEvent::new("late", action::CREATED));
let first = rx.try_recv().unwrap();
assert_eq!(
first.sequence, 3,
"the first received sequence exceeds 1, disclosing the missed history"
);
}
}