1use std::collections::HashMap;
33use std::sync::{Arc, Mutex, MutexGuard};
34use tokio::sync::broadcast;
35
36struct Room<T> {
37 tx: broadcast::Sender<T>,
38 subscribers: usize,
39}
40
41struct Shared<T> {
42 rooms: Mutex<HashMap<String, Room<T>>>,
43 capacity: usize,
44}
45
46pub struct Hub<T> {
48 shared: Arc<Shared<T>>,
49}
50
51impl<T> Clone for Hub<T> {
52 fn clone(&self) -> Self {
53 Self {
54 shared: self.shared.clone(),
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum Delivery<T> {
62 Message(T),
63 Missed(u64),
65}
66
67pub struct Subscription<T> {
69 rx: broadcast::Receiver<T>,
70 room: String,
71 hub: Hub<T>,
72}
73
74impl<T: Clone + Send + 'static> Hub<T> {
75 pub fn new(capacity: usize) -> Self {
77 Self {
78 shared: Arc::new(Shared {
79 rooms: Mutex::new(HashMap::new()),
80 capacity: capacity.max(1),
81 }),
82 }
83 }
84
85 fn lock(&self) -> MutexGuard<'_, HashMap<String, Room<T>>> {
86 self.shared.rooms.lock().unwrap_or_else(|e| e.into_inner())
87 }
88
89 pub fn subscribe(&self, room: &str) -> Subscription<T> {
91 let mut rooms = self.lock();
92 let entry = rooms.entry(room.to_string()).or_insert_with(|| Room {
93 tx: broadcast::channel(self.shared.capacity).0,
94 subscribers: 0,
95 });
96 entry.subscribers += 1;
97 Subscription {
98 rx: entry.tx.subscribe(),
99 room: room.to_string(),
100 hub: self.clone(),
101 }
102 }
103
104 pub fn publish(&self, room: &str, message: T) -> usize {
107 let rooms = self.lock();
108 rooms
109 .get(room)
110 .map(|r| r.tx.send(message).unwrap_or(0))
111 .unwrap_or(0)
112 }
113
114 pub fn members(&self, room: &str) -> usize {
116 self.lock().get(room).map_or(0, |r| r.subscribers)
117 }
118
119 pub fn rooms(&self) -> Vec<(String, usize)> {
121 let mut rooms: Vec<_> = self
122 .lock()
123 .iter()
124 .map(|(name, r)| (name.clone(), r.subscribers))
125 .collect();
126 rooms.sort();
127 rooms
128 }
129}
130
131impl<T: Clone + Send + 'static> Subscription<T> {
132 pub async fn recv(&mut self) -> Option<Delivery<T>> {
134 match self.rx.recv().await {
135 Ok(m) => Some(Delivery::Message(m)),
136 Err(broadcast::error::RecvError::Lagged(n)) => Some(Delivery::Missed(n)),
137 Err(broadcast::error::RecvError::Closed) => None,
138 }
139 }
140
141 pub fn room(&self) -> &str {
142 &self.room
143 }
144}
145
146impl<T> Drop for Subscription<T> {
147 fn drop(&mut self) {
148 let mut rooms = self
149 .hub
150 .shared
151 .rooms
152 .lock()
153 .unwrap_or_else(|e| e.into_inner());
154 if let Some(r) = rooms.get_mut(&self.room) {
155 r.subscribers = r.subscribers.saturating_sub(1);
156 if r.subscribers == 0 {
157 rooms.remove(&self.room);
158 }
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[tokio::test]
168 async fn rooms_are_isolated_and_counted() {
169 let hub: Hub<u32> = Hub::new(8);
170 let mut a1 = hub.subscribe("a");
171 let mut a2 = hub.subscribe("a");
172 let mut b = hub.subscribe("b");
173
174 assert_eq!(hub.publish("a", 1), 2);
175 assert_eq!(hub.publish("b", 2), 1);
176 assert_eq!(hub.publish("nobody", 3), 0);
177
178 assert_eq!(a1.recv().await, Some(Delivery::Message(1)));
179 assert_eq!(a2.recv().await, Some(Delivery::Message(1)));
180 assert_eq!(b.recv().await, Some(Delivery::Message(2)));
181 assert_eq!(hub.rooms(), [("a".to_string(), 2), ("b".to_string(), 1)]);
182 assert_eq!(a1.room(), "a");
183 }
184
185 #[tokio::test]
186 async fn late_joiners_only_see_new_messages_and_empty_rooms_vanish() {
187 let hub: Hub<&'static str> = Hub::new(8);
188 let early = hub.subscribe("r");
189 hub.publish("r", "before");
190 let mut late = hub.subscribe("r");
191 hub.publish("r", "after");
192 assert_eq!(late.recv().await, Some(Delivery::Message("after")));
193
194 drop(early);
195 assert_eq!(hub.members("r"), 1);
196 drop(late);
197 assert_eq!(hub.members("r"), 0);
198 assert!(
199 hub.rooms().is_empty(),
200 "the last member takes the room with it"
201 );
202 assert_eq!(hub.publish("r", "void"), 0);
203 }
204
205 #[tokio::test]
206 async fn slow_subscribers_learn_what_they_missed_and_do_not_block_others() {
207 let hub: Hub<u32> = Hub::new(2);
208 let mut slow = hub.subscribe("r");
209 let mut fast = hub.subscribe("r");
210 for i in 1..=5 {
211 hub.publish("r", i);
212 assert_eq!(fast.recv().await, Some(Delivery::Message(i)));
214 }
215 assert_eq!(slow.recv().await, Some(Delivery::Missed(3)));
217 assert_eq!(slow.recv().await, Some(Delivery::Message(4)));
218 assert_eq!(slow.recv().await, Some(Delivery::Message(5)));
219 }
220
221 #[tokio::test]
222 async fn clones_share_rooms_and_zero_capacity_is_bumped() {
223 let hub: Hub<u8> = Hub::new(0);
224 let other = hub.clone();
225 let mut sub = other.subscribe("x");
226 assert_eq!(hub.publish("x", 9), 1);
227 assert_eq!(sub.recv().await, Some(Delivery::Message(9)));
228 }
229}