1use std::{
2 collections::{HashMap, VecDeque},
3 sync::{
4 atomic::{AtomicU64, Ordering},
5 Arc, Condvar, Mutex, Weak,
6 },
7};
8
9use crate::{error::EventSubscriptionError, event::ClientEvent};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct SubscriberConfig {
17 pub capacity: usize,
21 pub overflow: SubscriberOverflow,
23}
24
25impl Default for SubscriberConfig {
26 fn default() -> Self {
27 Self {
28 capacity: 1024,
29 overflow: SubscriberOverflow::DropNewest,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum SubscriberOverflow {
40 DropNewest,
42 DropOldest,
44 Disconnect,
46}
47
48#[derive(Debug, Clone)]
54pub struct EventHub {
55 inner: Arc<HubInner>,
56}
57
58#[derive(Debug)]
59struct HubInner {
60 next_id: AtomicU64,
61 subscribers: Mutex<HashMap<u64, Arc<SubscriberInner>>>,
62}
63
64#[must_use = "dropping the subscription unregisters it"]
70#[derive(Debug)]
71pub struct EventSubscription {
72 id: u64,
73 hub: Weak<HubInner>,
74 inner: Arc<SubscriberInner>,
75}
76
77#[derive(Debug)]
78struct SubscriberInner {
79 state: Mutex<SubscriberState>,
80 available: Condvar,
81 config: SubscriberConfig,
82}
83
84#[derive(Debug)]
85struct SubscriberState {
86 queue: VecDeque<ClientEvent>,
87 connected: bool,
88}
89
90impl EventHub {
91 pub fn new() -> Self {
93 Self {
94 inner: Arc::new(HubInner {
95 next_id: AtomicU64::new(1),
96 subscribers: Mutex::new(HashMap::new()),
97 }),
98 }
99 }
100
101 pub fn subscribe(
105 &self,
106 config: SubscriberConfig,
107 ) -> Result<EventSubscription, crate::ClientError> {
108 if config.capacity == 0 {
109 return Err(crate::ClientError::InvalidConfig {
110 reason: "subscriber capacity must be greater than zero".to_owned(),
111 });
112 }
113
114 let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
115 let inner = Arc::new(SubscriberInner {
116 state: Mutex::new(SubscriberState {
117 queue: VecDeque::with_capacity(config.capacity),
118 connected: true,
119 }),
120 available: Condvar::new(),
121 config,
122 });
123 self.inner
124 .subscribers
125 .lock()
126 .expect("event hub mutex poisoned")
127 .insert(id, inner.clone());
128
129 Ok(EventSubscription {
130 id,
131 hub: Arc::downgrade(&self.inner),
132 inner,
133 })
134 }
135
136 pub fn publish(&self, event: ClientEvent) {
142 let subscribers: Vec<(u64, Arc<SubscriberInner>)> = self
143 .inner
144 .subscribers
145 .lock()
146 .expect("event hub mutex poisoned")
147 .iter()
148 .map(|(id, subscriber)| (*id, subscriber.clone()))
149 .collect();
150
151 let mut disconnected = Vec::new();
152 for (id, subscriber) in subscribers {
153 if !subscriber.publish(event.clone()) {
156 disconnected.push(id);
157 }
158 }
159
160 if disconnected.is_empty() {
161 return;
162 }
163
164 let mut subscribers = self
165 .inner
166 .subscribers
167 .lock()
168 .expect("event hub mutex poisoned");
169 for id in disconnected {
170 subscribers.remove(&id);
171 }
172 }
173
174 pub fn disconnect_all(&self) {
177 let subscribers: Vec<Arc<SubscriberInner>> = self
178 .inner
179 .subscribers
180 .lock()
181 .expect("event hub mutex poisoned")
182 .drain()
183 .map(|(_, subscriber)| subscriber)
184 .collect();
185
186 for subscriber in subscribers {
187 subscriber.disconnect();
188 }
189 }
190
191 #[cfg(test)]
192 fn subscriber_count(&self) -> usize {
193 self.inner
194 .subscribers
195 .lock()
196 .expect("event hub mutex poisoned")
197 .len()
198 }
199}
200
201impl Default for EventHub {
202 fn default() -> Self {
203 Self::new()
204 }
205}
206
207impl EventSubscription {
208 pub fn recv(&self) -> Result<ClientEvent, EventSubscriptionError> {
214 let mut state = self.inner.state.lock().expect("subscriber mutex poisoned");
215 loop {
216 if let Some(event) = state.queue.pop_front() {
217 return Ok(event);
218 }
219 if !state.connected {
220 return Err(EventSubscriptionError::Disconnected);
221 }
222 state = self
223 .inner
224 .available
225 .wait(state)
226 .expect("subscriber mutex poisoned");
227 }
228 }
229
230 pub fn try_recv(&self) -> Result<Option<ClientEvent>, EventSubscriptionError> {
237 let mut state = self.inner.state.lock().expect("subscriber mutex poisoned");
238 if let Some(event) = state.queue.pop_front() {
239 return Ok(Some(event));
240 }
241 if !state.connected {
242 return Err(EventSubscriptionError::Disconnected);
243 }
244 Ok(None)
245 }
246}
247
248impl Drop for EventSubscription {
249 fn drop(&mut self) {
250 self.inner.disconnect();
251 if let Some(hub) = self.hub.upgrade() {
252 hub.subscribers
253 .lock()
254 .expect("event hub mutex poisoned")
255 .remove(&self.id);
256 }
257 }
258}
259
260impl SubscriberInner {
261 fn publish(&self, event: ClientEvent) -> bool {
262 let mut state = self.state.lock().expect("subscriber mutex poisoned");
263 if !state.connected {
264 return false;
265 }
266
267 if state.queue.len() < self.config.capacity {
268 state.queue.push_back(event);
269 self.available.notify_one();
270 return true;
271 }
272
273 match self.config.overflow {
274 SubscriberOverflow::DropNewest => true,
275 SubscriberOverflow::DropOldest => {
276 state.queue.pop_front();
277 state.queue.push_back(event);
278 self.available.notify_one();
279 true
280 }
281 SubscriberOverflow::Disconnect => {
282 state.queue.clear();
283 state.connected = false;
284 self.available.notify_all();
285 false
286 }
287 }
288 }
289
290 fn disconnect(&self) {
291 let mut state = self.state.lock().expect("subscriber mutex poisoned");
292 state.connected = false;
293 self.available.notify_all();
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::{ClientTimestamp, WarningKind};
301
302 fn event(n: usize) -> ClientEvent {
303 ClientEvent::Warning {
304 kind: WarningKind::UntrackedReply,
305 message: format!("event-{n}"),
306 at: ClientTimestamp::now(),
307 }
308 }
309
310 fn event_message(event: ClientEvent) -> String {
311 match event {
312 ClientEvent::Warning { message, .. } => message,
313 other => panic!("unexpected event: {other:?}"),
314 }
315 }
316
317 fn assert_next(
318 sub: &EventSubscription,
319 expected: Result<Option<&str>, EventSubscriptionError>,
320 ) {
321 match (sub.try_recv(), expected) {
322 (Ok(Some(event)), Ok(Some(message))) => assert_eq!(event_message(event), message),
323 (Ok(None), Ok(None)) => {}
324 (Err(actual), Err(expected)) => assert_eq!(actual, expected),
325 (actual, expected) => panic!("expected {expected:?}, got {actual:?}"),
326 }
327 }
328
329 #[test]
330 fn publishes_to_multiple_subscribers() {
331 let hub = EventHub::new();
332 let a = hub.subscribe(SubscriberConfig::default()).unwrap();
333 let b = hub.subscribe(SubscriberConfig::default()).unwrap();
334
335 hub.publish(event(1));
336
337 assert_eq!(event_message(a.try_recv().unwrap().unwrap()), "event-1");
338 assert_eq!(event_message(b.try_recv().unwrap().unwrap()), "event-1");
339 }
340
341 #[test]
342 fn full_subscriber_overflow_policy_is_applied() {
343 let cases = [
344 (
345 SubscriberOverflow::DropNewest,
346 Ok(Some("event-1")),
347 Ok(None),
348 ),
349 (
350 SubscriberOverflow::DropOldest,
351 Ok(Some("event-2")),
352 Ok(None),
353 ),
354 (
355 SubscriberOverflow::Disconnect,
356 Err(EventSubscriptionError::Disconnected),
357 Err(EventSubscriptionError::Disconnected),
358 ),
359 ];
360
361 for (overflow, first_expected, second_expected) in cases {
362 let hub = EventHub::new();
363 let sub = hub
364 .subscribe(SubscriberConfig {
365 capacity: 1,
366 overflow,
367 })
368 .unwrap();
369
370 hub.publish(event(1));
371 hub.publish(event(2));
372
373 assert_next(&sub, first_expected);
374 assert_next(&sub, second_expected);
375 }
376 }
377
378 #[test]
379 fn full_subscriber_does_not_prevent_other_delivery() {
380 let hub = EventHub::new();
381 let slow = hub
382 .subscribe(SubscriberConfig {
383 capacity: 1,
384 overflow: SubscriberOverflow::DropNewest,
385 })
386 .unwrap();
387 let fast = hub
388 .subscribe(SubscriberConfig {
389 capacity: 4,
390 overflow: SubscriberOverflow::DropNewest,
391 })
392 .unwrap();
393
394 hub.publish(event(1));
395 hub.publish(event(2));
396
397 assert_eq!(event_message(slow.try_recv().unwrap().unwrap()), "event-1");
398 assert_eq!(event_message(fast.try_recv().unwrap().unwrap()), "event-1");
399 assert_eq!(event_message(fast.try_recv().unwrap().unwrap()), "event-2");
400 }
401
402 #[test]
403 fn subscribing_after_events_does_not_replay() {
404 let hub = EventHub::new();
405 hub.publish(event(1));
406
407 let sub = hub.subscribe(SubscriberConfig::default()).unwrap();
408
409 assert!(sub.try_recv().unwrap().is_none());
410 }
411
412 #[test]
413 fn dropping_subscription_unregisters_from_hub() {
414 let hub = EventHub::new();
415 let sub = hub.subscribe(SubscriberConfig::default()).unwrap();
416 assert_eq!(hub.subscriber_count(), 1);
417
418 drop(sub);
419
420 assert_eq!(hub.subscriber_count(), 0);
421 }
422
423 #[test]
424 fn disconnect_all_wakes_blocking_receivers() {
425 let hub = EventHub::new();
426 let sub = hub.subscribe(SubscriberConfig::default()).unwrap();
427 hub.disconnect_all();
428
429 assert_eq!(
430 sub.recv().unwrap_err(),
431 EventSubscriptionError::Disconnected
432 );
433 }
434
435 #[test]
436 fn session_closed_event_can_be_queued() {
437 let hub = EventHub::new();
438 let sub = hub.subscribe(SubscriberConfig::default()).unwrap();
439 hub.publish(ClientEvent::SessionClosed {
440 remote: "127.0.0.1:1".parse().unwrap(),
441 token: 1,
442 at: ClientTimestamp::now(),
443 });
444
445 assert!(matches!(
446 sub.recv().unwrap(),
447 ClientEvent::SessionClosed { token: 1, .. }
448 ));
449 }
450}