1use crate::canonical_message::tracing_support::LazyMessageIds;
6use crate::traits::{
7 BatchCommitFunc, ConsumerError, EndpointStatus, MessageConsumer, MessageDisposition,
8 ReceivedBatch,
9};
10use crate::CanonicalMessage;
11use async_trait::async_trait;
12use once_cell::sync::Lazy;
13use std::any::Any;
14use std::collections::{HashMap, VecDeque};
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::{Arc, Mutex, RwLock};
17use std::time::{Duration, SystemTime, UNIX_EPOCH};
18use tokio::sync::watch;
19use tracing::{debug, info, trace, warn};
20
21#[derive(Debug, Clone)]
22pub struct RetentionPolicy {
23 pub max_age: Option<Duration>,
25 pub max_count: Option<usize>,
27 pub subscriber_timeout: Duration,
29 pub gc_interval: Duration,
31}
32
33impl Default for RetentionPolicy {
34 fn default() -> Self {
35 Self {
36 max_age: Some(Duration::from_secs(3600 * 24)), max_count: Some(100_000),
38 subscriber_timeout: Duration::from_secs(300), gc_interval: Duration::from_secs(1),
40 }
41 }
42}
43
44static RUNTIME_EVENT_STORES: Lazy<Mutex<HashMap<String, Arc<EventStore>>>> =
46 Lazy::new(|| Mutex::new(HashMap::new()));
47
48pub fn get_or_create_event_store(topic: &str) -> Arc<EventStore> {
50 let mut stores = RUNTIME_EVENT_STORES.lock().unwrap();
51 stores
52 .entry(topic.to_string())
53 .or_insert_with(|| {
54 info!(topic = %topic, "Creating new runtime event store");
55 Arc::new(EventStore::new(Default::default()))
56 })
57 .clone()
58}
59
60pub fn event_store_exists(topic: &str) -> bool {
62 let stores = RUNTIME_EVENT_STORES.lock().unwrap();
63 stores.contains_key(topic)
64}
65
66#[derive(Debug)]
67struct SubscriberState {
68 last_ack_offset: AtomicU64,
69 last_seen: Mutex<SystemTime>,
70}
71
72#[derive(Debug, Clone)]
73pub struct StoredEvent {
74 pub message: CanonicalMessage,
75 pub offset: u64,
76 pub stored_at: u64,
77}
78
79pub struct EventStore {
82 events: RwLock<VecDeque<StoredEvent>>,
83 subscribers: RwLock<HashMap<String, Arc<SubscriberState>>>,
84 next_offset: AtomicU64,
85 base_offset: AtomicU64,
86 retention_policy: RetentionPolicy,
87 offset_update: watch::Sender<u64>,
88 dropped_events: AtomicU64,
89 last_gc: RwLock<SystemTime>,
90 on_drop: Option<Box<dyn Fn(Vec<StoredEvent>) + Send + Sync>>,
91}
92
93impl std::fmt::Debug for EventStore {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("EventStore")
96 .field("next_offset", &self.next_offset)
97 .field("base_offset", &self.base_offset)
98 .field("retention_policy", &self.retention_policy)
99 .finish_non_exhaustive()
100 }
101}
102
103impl EventStore {
104 pub fn new(retention_policy: RetentionPolicy) -> Self {
105 let (offset_update, _) = watch::channel(0);
106 Self {
107 events: RwLock::new(VecDeque::new()),
108 subscribers: RwLock::new(HashMap::new()),
109 next_offset: AtomicU64::new(1), base_offset: AtomicU64::new(1),
111 retention_policy,
112 offset_update,
113 dropped_events: AtomicU64::new(0),
114 last_gc: RwLock::new(SystemTime::now()),
115 on_drop: None,
116 }
117 }
118
119 pub async fn append(&self, event: CanonicalMessage) -> u64 {
122 let timestamp = SystemTime::now()
123 .duration_since(UNIX_EPOCH)
124 .unwrap_or_default()
125 .as_millis() as u64;
126
127 let mut events = self.events.write().unwrap();
128 let offset = self.next_offset.fetch_add(1, Ordering::SeqCst);
129 let stored = StoredEvent {
130 message: event,
131 offset,
132 stored_at: timestamp,
133 };
134
135 events.push_back(stored);
136
137 let mut removed = Vec::new();
138 let mut remove_count = 0;
139 let mut total_dropped = 0;
140 let mut max_count = 0;
141
142 if let Some(max) = self.retention_policy.max_count {
144 max_count = max;
145 if events.len() > max {
146 remove_count = events.len() - max;
147 removed = events.drain(0..remove_count).collect();
148 self.base_offset
149 .fetch_add(remove_count as u64, Ordering::SeqCst);
150 total_dropped = self
151 .dropped_events
152 .fetch_add(remove_count as u64, Ordering::SeqCst)
153 + remove_count as u64;
154 }
155 }
156 drop(events);
157
158 if remove_count > 0 {
159 if let Some(cb) = &self.on_drop {
160 cb(removed);
161 }
162 warn!(
163 "Retention policy enforced (max_count={}): dropped {} events (total dropped: {}). Slow subscribers may miss events.",
164 max_count, remove_count, total_dropped
165 );
166 }
167
168 trace!("Appended event offset {}", offset);
169 let _ = self.offset_update.send(offset);
171
172 offset
173 }
174
175 pub async fn append_batch(&self, messages: Vec<CanonicalMessage>) -> u64 {
177 if messages.is_empty() {
178 return self.next_offset.load(Ordering::SeqCst).saturating_sub(1);
179 }
180
181 let timestamp = SystemTime::now()
182 .duration_since(UNIX_EPOCH)
183 .unwrap_or_default()
184 .as_millis() as u64;
185
186 let count = messages.len() as u64;
187
188 let mut events = self.events.write().unwrap();
189 let start_offset = self.next_offset.fetch_add(count, Ordering::SeqCst);
190
191 let stored_events = messages
192 .into_iter()
193 .enumerate()
194 .map(|(i, event)| StoredEvent {
195 message: event,
196 offset: start_offset + i as u64,
197 stored_at: timestamp,
198 });
199
200 events.extend(stored_events);
201
202 let last_offset = start_offset + count - 1;
203
204 let mut removed = Vec::new();
205 let mut remove_count = 0;
206 let mut total_dropped = 0;
207 let mut max_count = 0;
208
209 if let Some(max) = self.retention_policy.max_count {
211 max_count = max;
212 if events.len() > max {
213 remove_count = events.len() - max;
214 removed = events.drain(0..remove_count).collect();
215 self.base_offset
216 .fetch_add(remove_count as u64, Ordering::SeqCst);
217 total_dropped = self
218 .dropped_events
219 .fetch_add(remove_count as u64, Ordering::SeqCst)
220 + remove_count as u64;
221 }
222 }
223 drop(events);
224
225 if remove_count > 0 {
226 if let Some(cb) = &self.on_drop {
227 cb(removed);
228 }
229 warn!(
230 "Retention policy enforced (max_count={}): dropped {} events (total dropped: {}). Slow subscribers may miss events.",
231 max_count, remove_count, total_dropped
232 );
233 }
234
235 trace!("Appended batch up to offset {}", last_offset);
236 let _ = self.offset_update.send(last_offset);
238
239 last_offset
240 }
241
242 pub fn with_drop_callback(
243 mut self,
244 callback: impl Fn(Vec<StoredEvent>) + Send + Sync + 'static,
245 ) -> Self {
246 self.on_drop = Some(Box::new(callback));
247 self
248 }
249
250 pub async fn register_subscriber(&self, subscriber_id: String) {
252 {
254 let subs = self.subscribers.read().unwrap();
255 if let Some(state) = subs.get(&subscriber_id) {
256 if let Ok(mut last_seen) = state.last_seen.lock() {
257 *last_seen = SystemTime::now();
258 }
259 return;
260 }
261 }
262
263 let mut subs = self.subscribers.write().unwrap();
265 subs.entry(subscriber_id).or_insert_with(|| {
266 Arc::new(SubscriberState {
267 last_ack_offset: AtomicU64::new(0),
268 last_seen: Mutex::new(SystemTime::now()),
269 })
270 });
271 }
272
273 pub async fn ack(&self, subscriber_id: &str, offset: u64) {
276 {
277 let subs = self.subscribers.read().unwrap();
278 if let Some(s) = subs.get(subscriber_id) {
279 s.last_ack_offset.fetch_max(offset, Ordering::SeqCst);
280 if let Ok(mut last_seen) = s.last_seen.lock() {
281 *last_seen = SystemTime::now();
282 }
283 }
284 }
285
286 trace!("Subscriber {} acked offset {}", subscriber_id, offset);
287
288 let now = SystemTime::now();
289 let should_gc = {
290 let last = self.last_gc.read().unwrap();
291 now.duration_since(*last).unwrap_or(Duration::ZERO) >= self.retention_policy.gc_interval
292 };
293
294 if should_gc {
295 let perform_gc = {
296 let mut last = self.last_gc.write().unwrap();
297 if now.duration_since(*last).unwrap_or(Duration::ZERO)
298 >= self.retention_policy.gc_interval
299 {
300 *last = now;
301 true
302 } else {
303 false
304 }
305 };
306
307 if perform_gc {
308 self.run_gc().await;
309 }
310 }
311 }
312
313 pub async fn get_events_since(
318 &self,
319 last_known_offset: u64,
320 limit: usize,
321 ) -> Result<Vec<StoredEvent>, crate::errors::ConsumerError> {
322 let events = self.events.read().unwrap();
323 let base = self.base_offset.load(Ordering::SeqCst);
324
325 let start_offset = last_known_offset + 1;
327
328 if start_offset < base {
330 return Err(crate::errors::ConsumerError::Gap {
331 requested: start_offset,
332 base,
333 });
334 }
335
336 let start_index = if start_offset > base {
338 (start_offset - base) as usize
339 } else {
340 0
341 };
342
343 if start_index >= events.len() {
344 return Ok(Vec::new());
345 }
346
347 let count = std::cmp::min(limit, events.len() - start_index);
348 let mut result = Vec::with_capacity(count);
349
350 let (s1, s2) = events.as_slices();
351
352 if start_index < s1.len() {
353 let take_s1 = std::cmp::min(count, s1.len() - start_index);
354 result.extend_from_slice(&s1[start_index..start_index + take_s1]);
355
356 let remaining = count - take_s1;
357 if remaining > 0 {
358 result.extend_from_slice(&s2[0..remaining]);
359 }
360 } else {
361 let s2_index = start_index - s1.len();
362 result.extend_from_slice(&s2[s2_index..s2_index + count]);
363 }
364
365 Ok(result)
366 }
367
368 pub async fn wait_for_events(&self, last_known_offset: u64) {
370 let mut rx = self.offset_update.subscribe();
371 if *rx.borrow() > last_known_offset {
372 return;
373 }
374 while rx.changed().await.is_ok() {
375 if *rx.borrow() > last_known_offset {
376 return;
377 }
378 }
379 }
380
381 async fn run_gc(&self) {
382 let subs = self.subscribers.read().unwrap();
383 if subs.is_empty() {
384 return;
385 }
386
387 let now = SystemTime::now();
388 let timeout = self.retention_policy.subscriber_timeout;
389
390 let active_acks: Vec<u64> = subs
392 .values()
393 .filter(|s| {
394 let last_seen = s.last_seen.lock().unwrap();
395 if let Ok(age) = now.duration_since(*last_seen) {
396 age < timeout
397 } else {
398 false
399 }
400 })
401 .map(|s| s.last_ack_offset.load(Ordering::SeqCst))
402 .collect();
403
404 drop(subs);
405
406 let min_ack = if active_acks.is_empty() {
408 0
409 } else {
410 *active_acks.iter().min().unwrap()
411 };
412
413 let mut events = self.events.write().unwrap();
414 let base = self.base_offset.load(Ordering::SeqCst);
415 let max_age = self.retention_policy.max_age;
416 let mut remove_count = 0;
417
418 for (i, event) in events.iter().enumerate() {
419 let mut remove = false;
420 let offset = base + i as u64;
421
422 if !active_acks.is_empty() && offset <= min_ack {
424 remove = true;
425 }
426
427 if !remove {
429 if let Some(max_age) = max_age {
430 let event_time = UNIX_EPOCH + Duration::from_millis(event.stored_at);
431 if let Ok(age) = now.duration_since(event_time) {
432 if age > max_age {
433 remove = true;
434 }
435 }
436 }
437 }
438
439 if remove {
440 remove_count += 1;
441 } else {
442 break;
443 }
444 }
445
446 let mut removed = Vec::new();
447 if remove_count > 0 {
448 debug!("GC removing {} events", remove_count);
449 removed = events.drain(0..remove_count).collect();
450 self.base_offset
451 .fetch_add(remove_count as u64, Ordering::SeqCst);
452 }
453 drop(events);
454
455 if remove_count > 0 {
456 if let Some(cb) = &self.on_drop {
457 cb(removed);
458 }
459 }
460 }
461
462 pub fn consumer(self: &Arc<Self>, subscriber_id: String) -> EventStoreConsumer {
463 EventStoreConsumer {
464 store: self.clone(),
465 subscriber_id,
466 last_offset: Arc::new(AtomicU64::new(0)),
467 }
468 }
469}
470
471#[derive(Debug)]
472pub struct EventStoreConsumer {
473 store: Arc<EventStore>,
474 subscriber_id: String,
475 last_offset: Arc<AtomicU64>,
476}
477
478#[async_trait]
479impl MessageConsumer for EventStoreConsumer {
480 async fn receive_batch(&mut self, max_messages: usize) -> Result<ReceivedBatch, ConsumerError> {
481 self.store
482 .register_subscriber(self.subscriber_id.clone())
483 .await;
484
485 let last_offset_val = self.last_offset.load(Ordering::SeqCst);
486
487 let stored_events = match self
488 .store
489 .get_events_since(last_offset_val, max_messages)
490 .await
491 {
492 Ok(ev) => ev,
493 Err(e) => return Err(e),
494 };
495
496 let stored_events = if stored_events.is_empty() {
497 self.store.wait_for_events(last_offset_val).await;
499 match self
500 .store
501 .get_events_since(last_offset_val, max_messages)
502 .await
503 {
504 Ok(ev) => ev,
505 Err(e) => return Err(e),
506 }
507 } else {
508 stored_events
509 };
510
511 let mut new_offset = last_offset_val;
512 if let Some(last) = stored_events.last() {
513 new_offset = last.offset;
514 }
515
516 self.last_offset.store(new_offset, Ordering::SeqCst);
518
519 let events: Vec<CanonicalMessage> = stored_events.into_iter().map(|e| e.message).collect();
521 trace!(count = events.len(), subscriber_id = %self.subscriber_id, message_ids = ?LazyMessageIds(&events), "Received batch of events from store");
522
523 let store = self.store.clone();
524 let subscriber_id = self.subscriber_id.clone();
525 let last_offset_arc = self.last_offset.clone();
526
527 let commit: BatchCommitFunc = Box::new(move |dispositions| {
528 Box::pin(async move {
529 if dispositions
530 .iter()
531 .any(|d| matches!(d, MessageDisposition::Nack))
532 {
533 last_offset_arc.fetch_min(last_offset_val, Ordering::SeqCst);
534 } else {
535 store.ack(&subscriber_id, new_offset).await;
536 }
537 Ok(())
538 })
539 });
540
541 Ok(ReceivedBatch {
542 messages: events,
543 commit,
544 })
545 }
546
547 async fn status(&self) -> EndpointStatus {
548 let last = self.last_offset.load(Ordering::SeqCst);
549 let next = self.store.next_offset.load(Ordering::SeqCst);
550 let pending = next.saturating_sub(last).saturating_sub(1) as usize;
552
553 EndpointStatus {
554 healthy: true,
555 target: self.subscriber_id.clone(),
556 pending: Some(pending),
557 details: serde_json::json!({
558 "mode": "event_store",
559 "current_offset": last,
560 "head_offset": next.saturating_sub(1)
561 }),
562 ..Default::default()
563 }
564 }
565
566 fn as_any(&self) -> &dyn Any {
567 self
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::errors::ConsumerError;
575 use std::sync::Arc;
576 use tokio::time::{sleep, Duration};
577
578 #[tokio::test]
579 async fn test_event_store_lifecycle() {
580 let store = Arc::new(EventStore::new(RetentionPolicy::default()));
581
582 let id1 = store.append(CanonicalMessage::from("msg1")).await;
584 assert_eq!(id1, 1);
585 let id2 = store.append(CanonicalMessage::from("msg2")).await;
586 assert_eq!(id2, 2);
587
588 let events = store.get_events_since(0, 10).await.unwrap();
590 assert_eq!(events.len(), 2);
591 assert_eq!(events[0].offset, 1);
592 assert_eq!(events[1].offset, 2);
593
594 let events = store.get_events_since(1, 10).await.unwrap();
596 assert_eq!(events.len(), 1);
597 assert_eq!(events[0].offset, 2);
598 }
599
600 #[tokio::test]
601 async fn test_retention_policy_max_count() {
602 let policy = RetentionPolicy {
603 max_count: Some(2),
604 ..Default::default()
605 };
606 let store = EventStore::new(policy);
607
608 store.append(CanonicalMessage::from("1")).await;
609 store.append(CanonicalMessage::from("2")).await;
610 store.append(CanonicalMessage::from("3")).await;
611
612 let res = store.get_events_since(0, 10).await;
615 match res {
616 Err(ConsumerError::Gap { requested, base }) => {
617 assert_eq!(requested, 1);
618 assert_eq!(base, 2);
619 }
620 _ => panic!("Expected Gap error"),
621 }
622
623 let events = store.get_events_since(1, 10).await.unwrap();
624 assert_eq!(events.len(), 2);
625 assert_eq!(events[0].message.get_payload_str(), "2");
626 assert_eq!(events[1].message.get_payload_str(), "3");
627 }
628
629 #[tokio::test]
630 async fn test_subscriber_ack_gc() {
631 let policy = RetentionPolicy {
632 max_count: None,
633 gc_interval: Duration::from_millis(10), subscriber_timeout: Duration::from_secs(60),
635 max_age: None,
636 };
637 let store = Arc::new(EventStore::new(policy));
638
639 store.append(CanonicalMessage::from("1")).await; store.append(CanonicalMessage::from("2")).await; store.register_subscriber("subA".into()).await;
644
645 store.ack("subA", 1).await;
647
648 sleep(Duration::from_millis(20)).await;
651 store.run_gc().await;
652
653 let res = store.get_events_since(0, 10).await;
655 match res {
656 Err(ConsumerError::Gap { requested, base }) => {
657 assert_eq!(requested, 1);
658 assert_eq!(base, 2);
659 }
660 _ => panic!("Expected Gap error, got {:?}", res),
661 }
662
663 let events = store.get_events_since(1, 10).await.unwrap();
664 assert_eq!(events.len(), 1);
665 assert_eq!(events[0].offset, 2);
666 }
667
668 #[tokio::test]
669 async fn test_append_batch() {
670 let store = Arc::new(EventStore::new(RetentionPolicy::default()));
671 let messages = vec![
672 CanonicalMessage::from("batch1"),
673 CanonicalMessage::from("batch2"),
674 ];
675 let last_offset = store.append_batch(messages).await;
676 assert_eq!(last_offset, 2);
677
678 let events = store.get_events_since(0, 10).await.unwrap();
679 assert_eq!(events.len(), 2);
680 assert_eq!(events[0].offset, 1);
681 assert_eq!(events[1].offset, 2);
682 }
683
684 #[tokio::test]
685 async fn test_retention_policy_max_age() {
686 let policy = RetentionPolicy {
687 max_age: Some(Duration::from_millis(50)),
688 gc_interval: Duration::from_millis(10),
689 max_count: None,
690 ..Default::default()
691 };
692 let store = Arc::new(EventStore::new(policy));
693 store.register_subscriber("subA".into()).await;
694
695 store.append(CanonicalMessage::from("1")).await;
696 sleep(Duration::from_millis(60)).await;
697 store.append(CanonicalMessage::from("2")).await;
698
699 store.ack("subA", 0).await;
701 sleep(Duration::from_millis(20)).await; store.run_gc().await;
703
704 let res = store.get_events_since(0, 10).await;
706 match res {
707 Err(ConsumerError::Gap { requested, base }) => {
708 assert_eq!(requested, 1);
709 assert_eq!(base, 2);
710 }
711 _ => panic!("Expected Gap error, got {:?}", res),
712 }
713 }
714
715 #[tokio::test]
716 async fn test_subscriber_timeout_gc() {
717 let policy = RetentionPolicy {
718 max_count: None,
719 gc_interval: Duration::from_millis(10),
720 subscriber_timeout: Duration::from_millis(50), max_age: None,
722 };
723 let store = Arc::new(EventStore::new(policy));
724
725 store.append(CanonicalMessage::from("1")).await; store.append(CanonicalMessage::from("2")).await; store.register_subscriber("subA".into()).await; store.register_subscriber("subB".into()).await; store.ack("subA", 1).await;
734
735 sleep(Duration::from_millis(60)).await;
737
738 store.ack("subB", 2).await;
740
741 store.run_gc().await;
743
744 let res = store.get_events_since(0, 10).await;
746 match res {
747 Err(ConsumerError::Gap { requested, base }) => {
748 assert_eq!(requested, 1);
749 assert_eq!(base, 3); }
751 _ => panic!("Expected Gap error, got {:?}", res),
752 }
753 }
754
755 #[tokio::test]
756 async fn test_event_store_consumer_waits_for_events() {
757 let store = Arc::new(EventStore::new(RetentionPolicy::default()));
758 let mut consumer = store.consumer("consumer1".to_string());
759
760 let producer_task = tokio::spawn({
761 let store = store.clone();
762 async move {
763 sleep(Duration::from_millis(50)).await;
764 store.append(CanonicalMessage::from("event1")).await;
765 }
766 });
767
768 let batch = consumer.receive_batch(1).await.unwrap();
770 assert_eq!(batch.messages.len(), 1);
771 assert_eq!(batch.messages[0].get_payload_str(), "event1");
772
773 (batch.commit)(vec![MessageDisposition::Ack]).await.unwrap();
775
776 producer_task.await.unwrap();
777 }
778
779 #[tokio::test]
780 async fn test_event_store_consumer_nack() {
781 let store = Arc::new(EventStore::new(RetentionPolicy::default()));
782 let mut consumer = store.consumer("consumer_nack".to_string());
783
784 store.append(CanonicalMessage::from("event1")).await;
785
786 let batch1 = consumer.receive_batch(1).await.unwrap();
788 (batch1.commit)(vec![MessageDisposition::Nack])
789 .await
790 .unwrap();
791
792 let batch2 = consumer.receive_batch(1).await.unwrap();
794 assert_eq!(batch2.messages[0].get_payload_str(), "event1");
795
796 (batch2.commit)(vec![MessageDisposition::Ack])
798 .await
799 .unwrap();
800 }
801}