use axum::response::sse::Event;
use dashmap::DashMap;
use futures::stream::Stream;
use std::convert::Infallible;
use std::sync::Arc;
use tokio::sync::broadcast;
use uuid::Uuid;
const CHANNEL_CAPACITY: usize = 256;
const MAX_EVENT_CHANNELS: usize = 10_000;
#[derive(Debug, Clone, serde::Serialize)]
#[non_exhaustive]
pub struct CrewEvent {
pub crew_id: String,
pub event_type: String,
pub data: serde_json::Value,
}
#[derive(Clone, Default)]
pub struct EventBus {
channels: Arc<DashMap<Uuid, broadcast::Sender<CrewEvent>>>,
}
impl EventBus {
pub fn new() -> Self {
Self {
channels: Arc::new(DashMap::new()),
}
}
pub fn sender(&self, crew_id: Uuid) -> broadcast::Sender<CrewEvent> {
if let Some(entry) = self.channels.get(&crew_id) {
return entry.clone();
}
if self.channels.len() >= MAX_EVENT_CHANNELS {
self.cleanup_orphans();
if self.channels.len() >= MAX_EVENT_CHANNELS {
tracing::warn!(
channels = self.channels.len(),
limit = MAX_EVENT_CHANNELS,
"event bus at capacity after orphan cleanup"
);
}
}
self.channels
.entry(crew_id)
.or_insert_with(|| broadcast::channel(CHANNEL_CAPACITY).0)
.clone()
}
pub fn subscribe(&self, crew_id: Uuid) -> broadcast::Receiver<CrewEvent> {
self.sender(crew_id).subscribe()
}
pub fn remove(&self, crew_id: Uuid) {
self.channels.remove(&crew_id);
}
#[must_use]
pub fn has(&self, crew_id: Uuid) -> bool {
self.channels.contains_key(&crew_id)
}
#[must_use]
pub fn len(&self) -> usize {
self.channels.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.channels.is_empty()
}
pub fn cleanup_orphans(&self) {
self.channels
.retain(|_id, sender| sender.receiver_count() > 0);
}
}
pub fn event_stream(
mut rx: broadcast::Receiver<CrewEvent>,
) -> impl Stream<Item = Result<Event, Infallible>> {
async_stream::stream! {
while let Ok(event) = rx.recv().await {
let data = match serde_json::to_string(&event) {
Ok(json) => json,
Err(e) => {
tracing::warn!(error = %e, "SSE event serialization failed");
"{\"error\":\"serialization failed\"}".to_string()
}
};
yield Ok(Event::default()
.event(event.event_type.clone())
.data(data));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn event_bus_new_is_empty() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let _rx = bus.subscribe(id);
}
#[test]
fn event_bus_sender_creates_channel() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let tx = bus.sender(id);
let _ = tx.send(CrewEvent {
crew_id: id.to_string(),
event_type: "test".into(),
data: serde_json::json!({}),
});
}
#[test]
fn event_bus_subscribe_receives_events() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let mut rx = bus.subscribe(id);
let tx = bus.sender(id);
tx.send(CrewEvent {
crew_id: id.to_string(),
event_type: "task_started".into(),
data: serde_json::json!({"task": "a"}),
})
.unwrap();
let event = rx.try_recv().unwrap();
assert_eq!(event.event_type, "task_started");
assert_eq!(event.crew_id, id.to_string());
}
#[test]
fn event_bus_remove_cleans_channel() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let _tx = bus.sender(id);
bus.remove(id);
let mut rx = bus.subscribe(id);
assert!(rx.try_recv().is_err());
}
#[test]
fn event_bus_independent_crews() {
let bus = EventBus::new();
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
let mut rx1 = bus.subscribe(id1);
let mut rx2 = bus.subscribe(id2);
bus.sender(id1)
.send(CrewEvent {
crew_id: id1.to_string(),
event_type: "e1".into(),
data: serde_json::json!(null),
})
.unwrap();
assert!(rx1.try_recv().is_ok());
assert!(rx2.try_recv().is_err());
}
#[test]
fn crew_event_serializes() {
let event = CrewEvent {
crew_id: "abc".into(),
event_type: "task_completed".into(),
data: serde_json::json!({"status": "ok"}),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("task_completed"));
assert!(json.contains("abc"));
}
#[test]
fn cleanup_orphans_removes_channels_with_no_receivers() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let tx = bus.sender(id);
let rx = tx.subscribe();
drop(rx);
assert!(bus.has(id));
bus.cleanup_orphans();
assert!(
!bus.has(id),
"orphan channel should be removed after cleanup"
);
}
#[test]
fn channel_capacity_boundary() {
let bus = EventBus::new();
let ids: Vec<Uuid> = (0..100).map(|_| Uuid::new_v4()).collect();
for &id in &ids {
let _tx = bus.sender(id);
let _rx = bus.subscribe(id);
}
assert_eq!(bus.len(), 100);
for &id in &ids {
bus.remove(id);
}
assert!(
bus.is_empty(),
"bus should be empty after removing all channels"
);
}
#[test]
fn broadcast_channel_overflow_causes_lagged_error() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let mut rx = bus.subscribe(id);
let tx = bus.sender(id);
for i in 0..=CHANNEL_CAPACITY {
let _ = tx.send(CrewEvent {
crew_id: id.to_string(),
event_type: format!("evt_{i}"),
data: serde_json::json!(null),
});
}
match rx.try_recv() {
Err(broadcast::error::TryRecvError::Lagged(_)) => {} other => panic!("expected Lagged error, got {other:?}"),
}
}
#[test]
fn concurrent_subscribers_same_crew() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let mut rx1 = bus.subscribe(id);
let mut rx2 = bus.subscribe(id);
let mut rx3 = bus.subscribe(id);
bus.sender(id)
.send(CrewEvent {
crew_id: id.to_string(),
event_type: "shared".into(),
data: serde_json::json!({"v": 1}),
})
.unwrap();
assert_eq!(rx1.try_recv().unwrap().event_type, "shared");
assert_eq!(rx2.try_recv().unwrap().event_type, "shared");
assert_eq!(rx3.try_recv().unwrap().event_type, "shared");
}
#[test]
fn event_isolation_between_crews() {
let bus = EventBus::new();
let crew_a = Uuid::new_v4();
let crew_b = Uuid::new_v4();
let _rx_a = bus.subscribe(crew_a);
let mut rx_b = bus.subscribe(crew_b);
bus.sender(crew_a)
.send(CrewEvent {
crew_id: crew_a.to_string(),
event_type: "only_a".into(),
data: serde_json::json!(null),
})
.unwrap();
assert!(
rx_b.try_recv().is_err(),
"crew B subscriber must not receive crew A events"
);
}
#[test]
fn sender_idempotent_for_same_crew() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let tx1 = bus.sender(id);
let tx2 = bus.sender(id);
let mut rx = tx2.subscribe();
tx1.send(CrewEvent {
crew_id: id.to_string(),
event_type: "dup".into(),
data: serde_json::json!(null),
})
.unwrap();
assert_eq!(rx.try_recv().unwrap().event_type, "dup");
assert_eq!(bus.len(), 1);
}
#[test]
fn has_returns_false_after_remove() {
let bus = EventBus::new();
let id = Uuid::new_v4();
let _tx = bus.sender(id);
assert!(bus.has(id), "channel should exist after sender()");
bus.remove(id);
assert!(!bus.has(id), "channel should not exist after remove()");
}
}