1use std::collections::BTreeMap;
2use std::sync::{
3 Arc, Mutex,
4 atomic::{AtomicU64, Ordering},
5};
6
7use roder_api::events::{EventEnvelope, EventSource, RoderEvent, ThreadId, TurnId};
8use time::OffsetDateTime;
9use tokio::sync::broadcast;
10
11#[derive(Debug, Clone, Default)]
12pub struct EventFilter {
13 pub thread_id: Option<ThreadId>,
14 pub turn_id: Option<TurnId>,
15 pub kinds: Vec<String>,
16 pub sources: Vec<EventSource>,
17}
18
19impl EventFilter {
20 pub fn matches(&self, envelope: &EventEnvelope) -> bool {
21 if let Some(thread_id) = &self.thread_id
22 && envelope.thread_id.as_ref() != Some(thread_id)
23 {
24 return false;
25 }
26 if let Some(turn_id) = &self.turn_id
27 && envelope.turn_id.as_ref() != Some(turn_id)
28 {
29 return false;
30 }
31 if !self.kinds.is_empty() && !self.kinds.iter().any(|kind| kind == &envelope.kind) {
32 return false;
33 }
34 if !self.sources.is_empty() && !self.sources.iter().any(|source| source == &envelope.source)
35 {
36 return false;
37 }
38 true
39 }
40}
41
42#[derive(Clone)]
43pub struct EventBus {
44 sender: broadcast::Sender<EventEnvelope>,
45 next_seq: Arc<AtomicU64>,
46 publication: Arc<Mutex<Publication>>,
47}
48
49struct Publication {
50 next: u64,
51 ready: BTreeMap<u64, Option<EventEnvelope>>,
52}
53
54pub(crate) struct PreparedEvent {
56 bus: EventBus,
57 envelope: EventEnvelope,
58 finished: bool,
59}
60
61impl PreparedEvent {
62 pub(crate) fn envelope(&self) -> &EventEnvelope {
63 &self.envelope
64 }
65
66 pub(crate) fn publish(mut self) {
67 self.finished = true;
68 self.bus
69 .finish(self.envelope.seq, Some(self.envelope.clone()));
70 }
71}
72
73impl Drop for PreparedEvent {
74 fn drop(&mut self) {
75 if !self.finished {
76 self.bus.finish(self.envelope.seq, None);
77 }
78 }
79}
80
81impl EventBus {
82 pub fn new(capacity: usize) -> Self {
83 let (sender, _) = broadcast::channel(capacity);
84 Self {
85 sender,
86 next_seq: Arc::new(AtomicU64::new(1)),
87 publication: Arc::new(Mutex::new(Publication {
88 next: 1,
89 ready: BTreeMap::new(),
90 })),
91 }
92 }
93
94 pub fn subscribe(&self) -> broadcast::Receiver<EventEnvelope> {
95 self.sender.subscribe()
96 }
97
98 pub fn emit(&self, event: RoderEvent) -> EventEnvelope {
99 let prepared = self.prepare(event);
100 let envelope = prepared.envelope().clone();
101 prepared.publish();
102 envelope
103 }
104
105 pub(crate) fn prepare(&self, event: RoderEvent) -> PreparedEvent {
106 PreparedEvent {
107 bus: self.clone(),
108 finished: false,
109 envelope: EventEnvelope {
110 event_id: uuid::Uuid::new_v4().to_string(),
111 seq: self.next_seq.fetch_add(1, Ordering::SeqCst),
112 timestamp: OffsetDateTime::now_utc(),
113 source: event.source(),
114 kind: event.kind().to_string(),
115 thread_id: event.thread_id().cloned(),
116 turn_id: event.turn_id().cloned(),
117 event,
118 },
119 }
120 }
121
122 fn finish(&self, sequence: u64, envelope: Option<EventEnvelope>) {
123 let mut publication = self
124 .publication
125 .lock()
126 .unwrap_or_else(|poisoned| poisoned.into_inner());
127 publication.ready.insert(sequence, envelope);
128 loop {
129 let next = publication.next;
130 let Some(envelope) = publication.ready.remove(&next) else {
131 break;
132 };
133 publication.next += 1;
134 if let Some(envelope) = envelope {
135 let _ = self.sender.send(envelope);
136 }
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use roder_api::events::{RoderEvent, TurnStarted};
145 use roder_api::inference::RuntimeProfile;
146
147 fn sample_event() -> RoderEvent {
148 RoderEvent::TurnStarted(TurnStarted {
149 thread_id: "thread".to_string(),
150 turn_id: "turn".to_string(),
151 runtime_profile: RuntimeProfile::Interactive,
152 timestamp: OffsetDateTime::now_utc(),
153 })
154 }
155
156 #[test]
157 fn prepared_events_preserve_order_and_abandoned_slots_do_not_block_subscribers() {
158 let bus = EventBus::new(16);
159 let mut receiver = bus.subscribe();
160 let first = bus.prepare(sample_event());
161 let second = bus.emit(sample_event());
162 assert!(receiver.try_recv().is_err());
163 let first_sequence = first.envelope().seq;
164 first.publish();
165 assert_eq!(receiver.try_recv().unwrap().seq, first_sequence);
166 assert_eq!(receiver.try_recv().unwrap().seq, second.seq);
167 let abandoned = bus.prepare(sample_event());
168 let following = bus.emit(sample_event());
169 assert!(receiver.try_recv().is_err());
170 drop(abandoned);
171 assert_eq!(receiver.try_recv().unwrap().seq, following.seq);
172 }
173
174 #[tokio::test]
175 async fn retains_burst_up_to_capacity_without_lagging() {
176 let capacity = 16_384;
181 let bus = EventBus::new(capacity);
182 let mut rx = bus.subscribe();
183
184 for _ in 0..capacity {
185 bus.emit(sample_event());
186 }
187
188 for _ in 0..capacity {
190 assert!(rx.try_recv().is_ok());
191 }
192 assert!(matches!(
193 rx.try_recv(),
194 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
195 ));
196 }
197
198 #[tokio::test]
199 async fn overflow_beyond_capacity_surfaces_lagged() {
200 let capacity = 16usize;
204 let bus = EventBus::new(capacity);
205 let mut rx = bus.subscribe();
206
207 for _ in 0..(capacity + 8) {
208 bus.emit(sample_event());
209 }
210
211 assert!(matches!(
212 rx.try_recv(),
213 Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_))
214 ));
215 }
216}