laminar_core/time/watermark/
mod.rs1use std::time::{Duration, Instant};
4
5use super::Watermark;
6
7pub trait WatermarkGenerator: Send {
13 fn on_event(&mut self, timestamp: i64) -> Option<Watermark>;
18
19 fn on_periodic(&mut self) -> Option<Watermark>;
23
24 fn current_watermark(&self) -> i64;
26
27 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark>;
33
34 fn restore_watermark_for_recovery(&mut self, timestamp: i64);
42
43 fn is_processing_time(&self) -> bool {
49 false
50 }
51}
52
53pub const DEFAULT_MAX_FUTURE_SKEW_MS: i64 = 5 * 60 * 1000;
55
56#[inline]
59fn is_grossly_future(timestamp: i64, skew_ms: i64) -> bool {
60 if skew_ms <= 0 {
61 return false;
62 }
63 let now = super::now_unix_millis();
64 now > 0 && timestamp > now.saturating_add(skew_ms)
65}
66
67pub struct BoundedOutOfOrdernessGenerator {
70 max_out_of_orderness: i64,
71 current_max_timestamp: i64,
72 current_watermark: i64,
73 max_future_skew_ms: i64,
75}
76
77impl BoundedOutOfOrdernessGenerator {
78 #[must_use]
84 pub fn new(max_out_of_orderness: i64) -> Self {
85 Self {
86 max_out_of_orderness,
87 current_max_timestamp: i64::MIN,
88 current_watermark: i64::MIN,
89 max_future_skew_ms: DEFAULT_MAX_FUTURE_SKEW_MS,
90 }
91 }
92
93 #[must_use]
95 pub fn with_max_future_skew(mut self, skew_ms: i64) -> Self {
96 self.max_future_skew_ms = skew_ms;
97 self
98 }
99
100 #[must_use]
102 #[allow(clippy::cast_possible_truncation)] pub fn from_duration(max_out_of_orderness: Duration) -> Self {
104 Self::new(max_out_of_orderness.as_millis() as i64)
105 }
106
107 #[must_use]
109 pub fn max_out_of_orderness(&self) -> i64 {
110 self.max_out_of_orderness
111 }
112}
113
114impl WatermarkGenerator for BoundedOutOfOrdernessGenerator {
115 #[inline]
116 fn on_event(&mut self, timestamp: i64) -> Option<Watermark> {
117 if is_grossly_future(timestamp, self.max_future_skew_ms) {
118 return None;
119 }
120 if timestamp > self.current_max_timestamp {
121 self.current_max_timestamp = timestamp;
122 let new_watermark = timestamp.saturating_sub(self.max_out_of_orderness);
123 if new_watermark > self.current_watermark {
124 self.current_watermark = new_watermark;
125 return Some(Watermark::new(new_watermark));
126 }
127 }
128 None
129 }
130
131 #[inline]
132 fn on_periodic(&mut self) -> Option<Watermark> {
133 None
135 }
136
137 #[inline]
138 fn current_watermark(&self) -> i64 {
139 self.current_watermark
140 }
141
142 #[inline]
143 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark> {
144 if is_grossly_future(timestamp, self.max_future_skew_ms) {
145 return None;
146 }
147 if timestamp > self.current_watermark {
148 self.current_watermark = timestamp;
149 let min_max = timestamp.saturating_add(self.max_out_of_orderness);
151 if min_max > self.current_max_timestamp {
152 self.current_max_timestamp = min_max;
153 }
154 Some(Watermark::new(timestamp))
155 } else {
156 None
157 }
158 }
159
160 #[inline]
161 fn restore_watermark_for_recovery(&mut self, timestamp: i64) {
162 self.current_watermark = timestamp;
163 self.current_max_timestamp = timestamp.saturating_add(self.max_out_of_orderness);
167 }
168}
169
170#[derive(Debug)]
173pub struct AscendingTimestampsGenerator {
174 current_watermark: i64,
175 max_future_skew_ms: i64,
177}
178
179impl Default for AscendingTimestampsGenerator {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185impl AscendingTimestampsGenerator {
186 #[must_use]
188 pub fn new() -> Self {
189 Self {
190 current_watermark: i64::MIN,
191 max_future_skew_ms: DEFAULT_MAX_FUTURE_SKEW_MS,
192 }
193 }
194
195 #[must_use]
197 pub fn with_max_future_skew(mut self, skew_ms: i64) -> Self {
198 self.max_future_skew_ms = skew_ms;
199 self
200 }
201}
202
203impl WatermarkGenerator for AscendingTimestampsGenerator {
204 #[inline]
205 fn on_event(&mut self, timestamp: i64) -> Option<Watermark> {
206 if is_grossly_future(timestamp, self.max_future_skew_ms) {
207 return None;
208 }
209 if timestamp > self.current_watermark {
210 self.current_watermark = timestamp;
211 Some(Watermark::new(timestamp))
212 } else {
213 None
214 }
215 }
216
217 #[inline]
218 fn on_periodic(&mut self) -> Option<Watermark> {
219 None
220 }
221
222 #[inline]
223 fn current_watermark(&self) -> i64 {
224 self.current_watermark
225 }
226
227 #[inline]
228 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark> {
229 if is_grossly_future(timestamp, self.max_future_skew_ms) {
230 return None;
231 }
232 if timestamp > self.current_watermark {
233 self.current_watermark = timestamp;
234 Some(Watermark::new(timestamp))
235 } else {
236 None
237 }
238 }
239
240 #[inline]
241 fn restore_watermark_for_recovery(&mut self, timestamp: i64) {
242 self.current_watermark = timestamp;
243 }
244}
245
246pub struct PeriodicGenerator<G: WatermarkGenerator> {
249 inner: G,
250 period: Duration,
251 last_emit_time: Instant,
252 last_emitted_watermark: i64,
253}
254
255impl<G: WatermarkGenerator> PeriodicGenerator<G> {
256 #[must_use]
263 pub fn new(inner: G, period: Duration) -> Self {
264 Self {
265 inner,
266 period,
267 last_emit_time: Instant::now(),
268 last_emitted_watermark: i64::MIN,
269 }
270 }
271
272 #[must_use]
274 pub fn inner(&self) -> &G {
275 &self.inner
276 }
277
278 pub fn inner_mut(&mut self) -> &mut G {
280 &mut self.inner
281 }
282}
283
284impl<G: WatermarkGenerator> WatermarkGenerator for PeriodicGenerator<G> {
285 fn on_event(&mut self, timestamp: i64) -> Option<Watermark> {
286 let wm = self.inner.on_event(timestamp);
287 if let Some(ref w) = wm {
288 self.last_emitted_watermark = w.timestamp();
289 self.last_emit_time = Instant::now();
290 }
291 wm
292 }
293
294 fn on_periodic(&mut self) -> Option<Watermark> {
295 if self.last_emit_time.elapsed() >= self.period {
297 let current = self.inner.current_watermark();
298 if current > self.last_emitted_watermark {
299 self.last_emitted_watermark = current;
300 self.last_emit_time = Instant::now();
301 return Some(Watermark::new(current));
302 }
303 self.last_emit_time = Instant::now();
304 }
305 None
306 }
307
308 fn current_watermark(&self) -> i64 {
309 self.inner.current_watermark()
310 }
311
312 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark> {
313 let wm = self.inner.advance_watermark(timestamp);
314 if let Some(ref w) = wm {
315 self.last_emitted_watermark = w.timestamp();
316 self.last_emit_time = Instant::now();
317 }
318 wm
319 }
320
321 fn restore_watermark_for_recovery(&mut self, timestamp: i64) {
322 self.inner.restore_watermark_for_recovery(timestamp);
323 self.last_emitted_watermark = timestamp;
324 self.last_emit_time = Instant::now();
325 }
326
327 fn is_processing_time(&self) -> bool {
328 self.inner.is_processing_time()
329 }
330}
331
332pub struct PunctuatedGenerator<F>
355where
356 F: Fn(i64) -> Option<Watermark> + Send,
357{
358 predicate: F,
359 current_watermark: i64,
360}
361
362impl<F> PunctuatedGenerator<F>
363where
364 F: Fn(i64) -> Option<Watermark> + Send,
365{
366 #[must_use]
372 pub fn new(predicate: F) -> Self {
373 Self {
374 predicate,
375 current_watermark: i64::MIN,
376 }
377 }
378}
379
380impl<F> WatermarkGenerator for PunctuatedGenerator<F>
381where
382 F: Fn(i64) -> Option<Watermark> + Send,
383{
384 fn on_event(&mut self, timestamp: i64) -> Option<Watermark> {
385 if let Some(wm) = (self.predicate)(timestamp) {
386 if wm.timestamp() > self.current_watermark {
387 self.current_watermark = wm.timestamp();
388 return Some(wm);
389 }
390 }
391 None
392 }
393
394 fn on_periodic(&mut self) -> Option<Watermark> {
395 None
396 }
397
398 fn current_watermark(&self) -> i64 {
399 self.current_watermark
400 }
401
402 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark> {
403 if timestamp > self.current_watermark {
404 self.current_watermark = timestamp;
405 Some(Watermark::new(timestamp))
406 } else {
407 None
408 }
409 }
410
411 fn restore_watermark_for_recovery(&mut self, timestamp: i64) {
412 self.current_watermark = timestamp;
413 }
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
418#[error(
419 "watermark recovery source count mismatch: expected {expected}, got {watermarks} watermarks and {idle_statuses} idle statuses"
420)]
421pub struct WatermarkRestoreError {
422 expected: usize,
423 watermarks: usize,
424 idle_statuses: usize,
425}
426
427#[derive(Debug)]
452pub struct WatermarkTracker {
453 source_watermarks: Vec<i64>,
455 combined_watermark: i64,
457 idle_sources: Vec<bool>,
459 last_activity: Vec<Instant>,
461 idle_timeout: Vec<Option<Duration>>,
464}
465
466impl WatermarkTracker {
467 #[must_use]
471 pub fn new(num_sources: usize) -> Self {
472 Self {
473 source_watermarks: vec![i64::MIN; num_sources],
474 combined_watermark: i64::MIN,
475 idle_sources: vec![false; num_sources],
476 last_activity: vec![Instant::now(); num_sources],
477 idle_timeout: vec![None; num_sources],
478 }
479 }
480
481 #[must_use]
483 pub fn with_idle_timeout(num_sources: usize, idle_timeout: Duration) -> Self {
484 Self {
485 source_watermarks: vec![i64::MIN; num_sources],
486 combined_watermark: i64::MIN,
487 idle_sources: vec![false; num_sources],
488 last_activity: vec![Instant::now(); num_sources],
489 idle_timeout: vec![Some(idle_timeout); num_sources],
490 }
491 }
492
493 pub fn set_idle_timeout(&mut self, source_id: usize, timeout: Option<Duration>) {
495 if let Some(slot) = self.idle_timeout.get_mut(source_id) {
496 *slot = timeout;
497 }
498 }
499
500 pub fn update_source(&mut self, source_id: usize, watermark: i64) -> Option<Watermark> {
504 if source_id >= self.source_watermarks.len() {
505 return None;
506 }
507
508 let was_idle = self.idle_sources[source_id];
509 self.idle_sources[source_id] = false;
511 self.last_activity[source_id] = Instant::now();
512
513 if watermark > self.source_watermarks[source_id] {
515 self.source_watermarks[source_id] = watermark;
516 self.update_combined()
517 } else if was_idle {
518 self.update_combined()
519 } else {
520 None
521 }
522 }
523
524 pub fn mark_idle(&mut self, source_id: usize) -> Option<Watermark> {
528 if source_id >= self.idle_sources.len() {
529 return None;
530 }
531
532 self.idle_sources[source_id] = true;
533 self.update_combined()
534 }
535
536 pub fn check_idle_sources(&mut self) -> Option<Watermark> {
540 let mut any_marked = false;
541 for i in 0..self.idle_sources.len() {
542 let Some(timeout) = self.idle_timeout[i] else {
543 continue; };
545 if !self.idle_sources[i] && self.last_activity[i].elapsed() >= timeout {
546 self.idle_sources[i] = true;
547 any_marked = true;
548 }
549 }
550 if any_marked {
551 self.update_combined()
552 } else {
553 None
554 }
555 }
556
557 pub fn restore_for_recovery(
576 &mut self,
577 source_watermarks: &[Option<i64>],
578 idle_sources: &[bool],
579 combined_watermark: Option<i64>,
580 ) -> Result<(), WatermarkRestoreError> {
581 let expected = self.source_watermarks.len();
582 if source_watermarks.len() != expected || idle_sources.len() != expected {
583 return Err(WatermarkRestoreError {
584 expected,
585 watermarks: source_watermarks.len(),
586 idle_statuses: idle_sources.len(),
587 });
588 }
589
590 for (target, recovered) in self
591 .source_watermarks
592 .iter_mut()
593 .zip(source_watermarks.iter())
594 {
595 *target = recovered.unwrap_or(i64::MIN);
596 }
597 self.idle_sources.copy_from_slice(idle_sources);
598 self.combined_watermark = combined_watermark.unwrap_or(i64::MIN);
599 let restored_at = Instant::now();
600 self.last_activity.fill(restored_at);
601 Ok(())
602 }
603
604 #[must_use]
606 pub fn current_watermark(&self) -> Option<Watermark> {
607 if self.combined_watermark == i64::MIN {
608 None
609 } else {
610 Some(Watermark::new(self.combined_watermark))
611 }
612 }
613
614 #[must_use]
616 pub fn source_watermark(&self, source_id: usize) -> Option<i64> {
617 self.source_watermarks.get(source_id).copied()
618 }
619
620 #[must_use]
622 pub fn is_idle(&self, source_id: usize) -> bool {
623 self.idle_sources.get(source_id).copied().unwrap_or(false)
624 }
625
626 #[must_use]
628 pub fn num_sources(&self) -> usize {
629 self.source_watermarks.len()
630 }
631
632 #[must_use]
634 pub fn active_source_count(&self) -> usize {
635 self.idle_sources.iter().filter(|&&idle| !idle).count()
636 }
637
638 fn update_combined(&mut self) -> Option<Watermark> {
640 let mut min_watermark = i64::MAX;
642 let mut has_active = false;
643
644 for (i, &wm) in self.source_watermarks.iter().enumerate() {
645 if !self.idle_sources[i] {
646 has_active = true;
647 min_watermark = min_watermark.min(wm);
648 }
649 }
650
651 if !has_active {
653 min_watermark = self
654 .source_watermarks
655 .iter()
656 .copied()
657 .max()
658 .unwrap_or(i64::MIN);
659 }
660
661 if min_watermark > self.combined_watermark && min_watermark != i64::MAX {
662 self.combined_watermark = min_watermark;
663 Some(Watermark::new(min_watermark))
664 } else {
665 None
666 }
667 }
668}
669
670pub struct SourceProvidedGenerator {
675 source_watermark: i64,
677 fallback: BoundedOutOfOrdernessGenerator,
679 prefer_source: bool,
681}
682
683impl SourceProvidedGenerator {
684 #[must_use]
691 pub fn new(fallback_lateness: i64, prefer_source: bool) -> Self {
692 Self {
693 source_watermark: i64::MIN,
694 fallback: BoundedOutOfOrdernessGenerator::new(fallback_lateness),
695 prefer_source,
696 }
697 }
698
699 pub fn on_source_watermark(&mut self, watermark: i64) -> Option<Watermark> {
703 if watermark > self.source_watermark {
704 self.source_watermark = watermark;
705 if self.prefer_source || watermark > self.fallback.current_watermark() {
706 return Some(Watermark::new(watermark));
707 }
708 }
709 None
710 }
711}
712
713impl WatermarkGenerator for SourceProvidedGenerator {
714 fn on_event(&mut self, timestamp: i64) -> Option<Watermark> {
715 let fallback_wm = self.fallback.on_event(timestamp);
716
717 if self.prefer_source {
718 if self.source_watermark > i64::MIN {
720 return None; }
722 }
723
724 fallback_wm
725 }
726
727 fn on_periodic(&mut self) -> Option<Watermark> {
728 None
729 }
730
731 fn current_watermark(&self) -> i64 {
732 if self.prefer_source && self.source_watermark > i64::MIN {
733 self.source_watermark
734 } else {
735 self.fallback.current_watermark().max(self.source_watermark)
736 }
737 }
738
739 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark> {
740 self.on_source_watermark(timestamp)
741 }
742
743 fn restore_watermark_for_recovery(&mut self, timestamp: i64) {
744 self.source_watermark = timestamp;
745 self.fallback.restore_watermark_for_recovery(timestamp);
746 }
747}
748
749pub struct ProcessingTimeGenerator {
771 current_watermark: i64,
772}
773
774impl ProcessingTimeGenerator {
775 #[must_use]
777 pub fn new() -> Self {
778 Self {
779 current_watermark: i64::MIN,
780 }
781 }
782}
783
784impl Default for ProcessingTimeGenerator {
785 fn default() -> Self {
786 Self::new()
787 }
788}
789
790impl WatermarkGenerator for ProcessingTimeGenerator {
791 #[inline]
792 fn on_event(&mut self, _timestamp: i64) -> Option<Watermark> {
793 None
795 }
796
797 #[inline]
798 fn on_periodic(&mut self) -> Option<Watermark> {
799 let now = super::now_unix_millis();
800 if now > self.current_watermark {
801 self.current_watermark = now;
802 Some(Watermark::new(now))
803 } else {
804 None
805 }
806 }
807
808 #[inline]
809 fn current_watermark(&self) -> i64 {
810 self.current_watermark
811 }
812
813 #[inline]
814 fn advance_watermark(&mut self, timestamp: i64) -> Option<Watermark> {
815 if timestamp > self.current_watermark {
816 self.current_watermark = timestamp;
817 Some(Watermark::new(timestamp))
818 } else {
819 None
820 }
821 }
822
823 #[inline]
824 fn restore_watermark_for_recovery(&mut self, timestamp: i64) {
825 self.current_watermark = timestamp;
826 }
827
828 #[inline]
829 fn is_processing_time(&self) -> bool {
830 true
831 }
832}
833
834#[cfg(test)]
835mod tests;