oxirs_stream/watermark/
late_handler.rs1use std::collections::HashMap;
24
25pub use super::{LateDataDecision, LateDataHandler, LateDataPolicy};
26
27#[derive(Debug, Default)]
34pub struct AllowedLatenessTracker {
35 windows: HashMap<String, (i64, i64)>,
37}
38
39impl AllowedLatenessTracker {
40 pub fn new() -> Self {
42 Self {
43 windows: HashMap::new(),
44 }
45 }
46
47 pub fn register(
49 &mut self,
50 window_id: impl Into<String>,
51 window_end_ms: i64,
52 allowed_lateness_ms: i64,
53 ) {
54 self.windows
55 .insert(window_id.into(), (window_end_ms, allowed_lateness_ms));
56 }
57
58 pub fn is_open(&self, window_id: &str, now_ms: i64) -> bool {
60 match self.windows.get(window_id) {
61 None => false,
62 Some(&(end_ms, lateness_ms)) => now_ms < end_ms.saturating_add(lateness_ms),
63 }
64 }
65
66 pub fn evict_closed(&mut self, now_ms: i64) -> Vec<String> {
69 let mut evicted = Vec::new();
70 self.windows.retain(|id, &mut (end_ms, lateness_ms)| {
71 let still_open = now_ms < end_ms.saturating_add(lateness_ms);
72 if !still_open {
73 evicted.push(id.clone());
74 }
75 still_open
76 });
77 evicted
78 }
79
80 pub fn len(&self) -> usize {
82 self.windows.len()
83 }
84
85 pub fn is_empty(&self) -> bool {
87 self.windows.is_empty()
88 }
89}
90
91#[derive(Debug, Default)]
95pub struct SideOutputRouter<E> {
96 channels: HashMap<String, Vec<E>>,
97}
98
99impl<E> SideOutputRouter<E> {
100 pub fn new() -> Self {
102 Self {
103 channels: HashMap::new(),
104 }
105 }
106
107 pub fn push(&mut self, channel: &str, event: E) {
109 self.channels
110 .entry(channel.to_string())
111 .or_default()
112 .push(event);
113 }
114
115 pub fn drain(&mut self, channel: &str) -> Vec<E> {
117 self.channels.remove(channel).unwrap_or_default()
118 }
119
120 pub fn len(&self, channel: &str) -> usize {
122 self.channels.get(channel).map(|v| v.len()).unwrap_or(0)
123 }
124
125 pub fn is_empty(&self, channel: &str) -> bool {
127 self.channels
128 .get(channel)
129 .map(|v| v.is_empty())
130 .unwrap_or(true)
131 }
132
133 pub fn channels(&self) -> impl Iterator<Item = &String> {
135 self.channels.keys()
136 }
137}
138
139#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn allowed_lateness_open_until_budget_exhausted() {
147 let mut t = AllowedLatenessTracker::new();
148 t.register("w1", 1_000, 500);
149 assert!(t.is_open("w1", 1_400));
150 assert!(!t.is_open("w1", 1_500));
151 assert!(!t.is_open("w1", 2_000));
152 }
153
154 #[test]
155 fn allowed_lateness_evict_closed() {
156 let mut t = AllowedLatenessTracker::new();
157 t.register("a", 100, 100);
158 t.register("b", 1_000, 100);
159 let evicted = t.evict_closed(500);
160 assert!(evicted.contains(&"a".to_string()));
161 assert!(!evicted.contains(&"b".to_string()));
162 assert_eq!(t.len(), 1);
163 }
164
165 #[test]
166 fn side_output_router_pushes_and_drains() {
167 let mut router: SideOutputRouter<i32> = SideOutputRouter::new();
168 router.push("late", 1);
169 router.push("late", 2);
170 router.push("dlq", 7);
171 assert_eq!(router.len("late"), 2);
172 assert_eq!(router.len("dlq"), 1);
173 let drained = router.drain("late");
174 assert_eq!(drained, vec![1, 2]);
175 assert!(router.is_empty("late"));
176 assert_eq!(router.len("dlq"), 1);
177 }
178
179 #[test]
180 fn re_export_late_data_handler_is_visible() {
181 let mut h = LateDataHandler::new(LateDataPolicy::Drop);
182 let d = h.handle(10, 50);
183 assert_eq!(d, LateDataDecision::Drop);
184 }
185}