1use std::collections::VecDeque;
28use std::sync::{Arc, Mutex};
29use std::time::{Duration, Instant};
30use tokio::sync::mpsc;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct StoredEvent {
35 pub id: String,
37 pub event_type: String,
39 pub data: String,
41}
42
43#[derive(Debug, Clone)]
45#[non_exhaustive]
46pub struct StreamConfig {
47 pub max_events_per_stream: usize,
49 pub max_age: Duration,
52 pub channel_capacity: usize,
55}
56
57impl StreamConfig {
58 #[must_use]
60 pub fn new() -> Self {
61 Self::default()
62 }
63
64 #[must_use]
66 pub const fn max_events_per_stream(mut self, max: usize) -> Self {
67 self.max_events_per_stream = max;
68 self
69 }
70
71 #[must_use]
73 pub const fn max_age(mut self, max_age: Duration) -> Self {
74 self.max_age = max_age;
75 self
76 }
77
78 #[must_use]
80 pub const fn channel_capacity(mut self, capacity: usize) -> Self {
81 self.channel_capacity = capacity;
82 self
83 }
84}
85
86impl Default for StreamConfig {
87 fn default() -> Self {
88 Self {
89 max_events_per_stream: 1000,
90 max_age: Duration::from_secs(300),
91 channel_capacity: 100,
92 }
93 }
94}
95
96#[derive(Debug)]
97struct StreamSlot {
98 id: u64,
99 seq: u64,
101 buffer: VecDeque<(Instant, StoredEvent)>,
102 sender: Option<mpsc::Sender<StoredEvent>>,
104 opened: Instant,
105 died: Option<Instant>,
106}
107
108impl StreamSlot {
109 fn store(&mut self, event_type: &str, data: String, config: &StreamConfig) -> StoredEvent {
110 let event = StoredEvent {
111 id: format!("{}-{}", self.id, self.seq),
112 event_type: event_type.to_string(),
113 data,
114 };
115 self.seq += 1;
116 self.buffer.push_back((Instant::now(), event.clone()));
117 while self.buffer.len() > config.max_events_per_stream {
118 self.buffer.pop_front();
119 }
120 while self
121 .buffer
122 .front()
123 .is_some_and(|(at, _)| at.elapsed() > config.max_age)
124 {
125 self.buffer.pop_front();
126 }
127 event
128 }
129}
130
131#[derive(Debug)]
134pub struct StreamRegistry {
135 inner: Mutex<Inner>,
136 config: StreamConfig,
137}
138
139#[derive(Debug)]
140struct Inner {
141 streams: Vec<StreamSlot>,
142 next_stream_id: u64,
143}
144
145#[derive(Debug)]
150pub struct StreamHandle {
151 stream_id: u64,
152 rx: mpsc::Receiver<StoredEvent>,
153 registry: Arc<StreamRegistry>,
154}
155
156impl StreamHandle {
157 #[must_use]
159 pub const fn stream_id(&self) -> u64 {
160 self.stream_id
161 }
162
163 pub async fn recv(&mut self) -> Option<StoredEvent> {
166 self.rx.recv().await
167 }
168}
169
170impl Drop for StreamHandle {
171 fn drop(&mut self) {
172 self.registry.mark_dead(self.stream_id);
173 }
174}
175
176impl StreamRegistry {
177 #[must_use]
179 pub fn new(config: StreamConfig) -> Self {
180 Self {
181 inner: Mutex::new(Inner {
182 streams: Vec::new(),
183 next_stream_id: 1,
184 }),
185 config,
186 }
187 }
188
189 pub fn open(
194 self: &Arc<Self>,
195 prime_event_type: &str,
196 prime_data: String,
197 ) -> (StreamHandle, StoredEvent) {
198 let (tx, rx) = mpsc::channel(self.config.channel_capacity);
199 let mut inner = self.inner.lock().expect("stream registry lock");
200 Self::reap(&mut inner, &self.config);
201 let id = inner.next_stream_id;
202 inner.next_stream_id += 1;
203 let mut slot = StreamSlot {
204 id,
205 seq: 0,
206 buffer: VecDeque::new(),
207 sender: Some(tx),
208 opened: Instant::now(),
209 died: None,
210 };
211 let prime = slot.store(prime_event_type, prime_data, &self.config);
212 inner.streams.push(slot);
213 drop(inner);
214 (
215 StreamHandle {
216 stream_id: id,
217 rx,
218 registry: Arc::clone(self),
219 },
220 prime,
221 )
222 }
223
224 pub fn resume(
232 self: &Arc<Self>,
233 last_event_id: &str,
234 ) -> Option<(StreamHandle, Vec<StoredEvent>)> {
235 let (stream_id, seq) = parse_event_id(last_event_id)?;
236 let (tx, rx) = mpsc::channel(self.config.channel_capacity);
237 let mut inner = self.inner.lock().expect("stream registry lock");
238 Self::reap(&mut inner, &self.config);
239 let slot = inner.streams.iter_mut().find(|s| s.id == stream_id)?;
240 slot.sender = Some(tx);
241 slot.died = None;
242 let replay = slot
243 .buffer
244 .iter()
245 .filter(|(_, e)| parse_event_id(&e.id).is_some_and(|(_, s)| s > seq))
246 .map(|(_, e)| e.clone())
247 .collect();
248 drop(inner);
249 Some((
250 StreamHandle {
251 stream_id,
252 rx,
253 registry: Arc::clone(self),
254 },
255 replay,
256 ))
257 }
258
259 #[must_use]
268 pub fn send(&self, event_type: &str, data: String) -> Option<String> {
269 let mut inner = self.inner.lock().expect("stream registry lock");
270 Self::reap(&mut inner, &self.config);
271 let config = &self.config;
272 let slot = inner
273 .streams
274 .iter_mut()
275 .filter(|s| s.sender.is_some())
276 .min_by_key(|s| s.opened)?;
277 let event = slot.store(event_type, data, config);
278 if let Some(sender) = &slot.sender {
279 match sender.try_send(event.clone()) {
280 Ok(()) => {}
281 Err(mpsc::error::TrySendError::Full(_) | mpsc::error::TrySendError::Closed(_)) => {
282 slot.sender = None;
285 slot.died = Some(Instant::now());
286 }
287 }
288 }
289 Some(event.id)
290 }
291
292 #[must_use]
294 pub fn has_live_stream(&self) -> bool {
295 self.inner
296 .lock()
297 .expect("stream registry lock")
298 .streams
299 .iter()
300 .any(|s| s.sender.is_some())
301 }
302
303 fn mark_dead(&self, stream_id: u64) {
304 if let Ok(mut inner) = self.inner.lock() {
305 if let Some(slot) = inner.streams.iter_mut().find(|s| s.id == stream_id) {
306 slot.sender = None;
307 slot.died = Some(Instant::now());
308 }
309 }
310 }
311
312 fn reap(inner: &mut Inner, config: &StreamConfig) {
314 inner.streams.retain(|s| {
315 s.sender.is_some() || s.died.is_none_or(|at| at.elapsed() < config.max_age)
316 });
317 }
318}
319
320fn parse_event_id(id: &str) -> Option<(u64, u64)> {
322 let (stream, seq) = id.split_once('-')?;
323 Some((stream.parse().ok()?, seq.parse().ok()?))
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 fn registry() -> Arc<StreamRegistry> {
331 Arc::new(StreamRegistry::new(StreamConfig::default()))
332 }
333
334 #[tokio::test]
335 async fn send_delivers_to_exactly_one_stream() {
336 let reg = registry();
337 let (mut a, _) = reg.open("connected", "sid".into());
338 let (mut b, _) = reg.open("connected", "sid".into());
339
340 let id = reg.send("message", "hello".into()).expect("live stream");
341 let got = a.recv().await.expect("delivered");
343 assert_eq!(got.id, id);
344 assert_eq!(got.data, "hello");
345 assert!(
347 tokio::time::timeout(Duration::from_millis(50), b.recv())
348 .await
349 .is_err(),
350 "second stream must not receive the message"
351 );
352 }
353
354 #[tokio::test]
355 async fn event_ids_encode_stream_and_sequence() {
356 let reg = registry();
357 let (_a, prime) = reg.open("connected", "sid".into());
358 assert_eq!(prime.id, "1-0");
359 let id1 = reg.send("message", "x".into()).unwrap();
360 let id2 = reg.send("message", "y".into()).unwrap();
361 assert_eq!(id1, "1-1");
362 assert_eq!(id2, "1-2");
363 }
364
365 #[tokio::test]
366 async fn resume_replays_only_same_stream_events_after_cursor() {
367 let reg = registry();
368 let (a, _) = reg.open("connected", "sid".into());
369 let id1 = reg.send("message", "one".into()).unwrap();
370 let _id2 = reg.send("message", "two".into()).unwrap();
371 drop(a); let (_b, _) = reg.open("connected", "sid".into());
375 let _ = reg.send("message", "other-stream".into()).unwrap();
376
377 let (_a2, replay) = reg.resume(&id1).expect("resumable");
378 assert_eq!(replay.len(), 1, "only events after the cursor: {replay:?}");
379 assert_eq!(replay[0].data, "two");
380 }
381
382 #[tokio::test]
383 async fn resumed_stream_keeps_designation() {
384 let reg = registry();
385 let (a, prime) = reg.open("connected", "sid".into());
386 let (_b, _) = reg.open("connected", "sid".into());
387 drop(a);
388
389 let (mut a2, _) = reg.resume(&prime.id).expect("resumable");
391 let id = reg.send("message", "after-resume".into()).unwrap();
392 assert!(
393 id.starts_with("1-"),
394 "designated must still be stream 1: {id}"
395 );
396 assert_eq!(a2.recv().await.unwrap().data, "after-resume");
397 }
398
399 #[tokio::test]
400 async fn overflow_kills_stream_and_replay_recovers() {
401 let reg = Arc::new(StreamRegistry::new(StreamConfig {
402 channel_capacity: 2,
403 ..StreamConfig::default()
404 }));
405 let (a, prime) = reg.open("connected", "sid".into());
406
407 let _ = reg.send("message", "m1".into()).unwrap();
409 let _ = reg.send("message", "m2".into()).unwrap();
410 let id3 = reg.send("message", "m3".into()).unwrap();
411 assert!(!reg.has_live_stream(), "overflow must kill the stream");
412
413 drop(a);
416 let (_a2, replay) = reg.resume(&prime.id).expect("resumable");
417 assert_eq!(replay.last().map(|e| e.id.as_str()), Some(id3.as_str()));
418 assert_eq!(replay.len(), 3);
419 }
420
421 #[tokio::test]
422 async fn no_live_stream_returns_none() {
423 let reg = registry();
424 assert!(reg.send("message", "x".into()).is_none());
425 let (a, _) = reg.open("connected", "sid".into());
426 drop(a);
427 assert!(
428 reg.send("message", "x".into()).is_none(),
429 "a dead stream is not a delivery target"
430 );
431 }
432
433 #[tokio::test]
434 async fn dead_stream_buffer_is_reaped_after_max_age() {
435 let reg = Arc::new(StreamRegistry::new(StreamConfig {
436 max_age: Duration::from_millis(10),
437 ..StreamConfig::default()
438 }));
439 let (a, prime) = reg.open("connected", "sid".into());
440 drop(a);
441 tokio::time::sleep(Duration::from_millis(30)).await;
442 let _ = reg.send("message", "x".into());
444 assert!(
445 reg.resume(&prime.id).is_none(),
446 "expired dead stream must not be resumable"
447 );
448 }
449
450 #[tokio::test]
451 async fn handle_drop_marks_stream_dead() {
452 let reg = registry();
453 let (a, _) = reg.open("connected", "sid".into());
454 assert!(reg.has_live_stream());
455 drop(a);
456 assert!(!reg.has_live_stream());
457 }
458}