1use std::collections::BTreeMap;
5use std::sync::Arc;
6use std::sync::atomic::AtomicU64;
7use std::sync::atomic::Ordering;
8
9use sui_futures::service::Service;
10use tokio::sync::SetOnce;
11use tokio::sync::mpsc;
12use tokio::time::MissedTickBehavior;
13use tokio::time::interval;
14use tracing::debug;
15use tracing::info;
16
17use crate::metrics::CheckpointLagMetricReporter;
18use crate::metrics::IndexerMetrics;
19use crate::pipeline::CommitterConfig;
20use crate::pipeline::IndexedCheckpoint;
21use crate::pipeline::WatermarkPart;
22use crate::pipeline::concurrent::BatchStatus;
23use crate::pipeline::concurrent::BatchedRows;
24use crate::pipeline::concurrent::Handler;
25
26struct PendingCheckpoint<H: Handler> {
29 values: std::vec::IntoIter<H::Value>,
31 watermark: WatermarkPart,
33}
34
35impl<H: Handler> PendingCheckpoint<H> {
36 fn is_empty(&self) -> bool {
38 let empty = self.values.len() == 0;
39 debug_assert!(!empty || self.watermark.batch_rows == 0);
40 empty
41 }
42}
43
44impl<H: Handler> From<IndexedCheckpoint<H>> for PendingCheckpoint<H> {
45 fn from(indexed: IndexedCheckpoint<H>) -> Self {
46 let total_rows = indexed.values.len();
47 Self {
48 watermark: WatermarkPart {
49 watermark: indexed.watermark,
50 batch_rows: total_rows,
51 total_rows,
52 },
53 values: indexed.values.into_iter(),
54 }
55 }
56}
57
58pub(super) fn collector<H: Handler>(
74 handler: Arc<H>,
75 config: CommitterConfig,
76 mut rx: mpsc::Receiver<IndexedCheckpoint<H>>,
77 tx: mpsc::Sender<BatchedRows<H>>,
78 main_reader_lo: Arc<SetOnce<AtomicU64>>,
79 metrics: Arc<IndexerMetrics>,
80 min_eager_rows: usize,
81 max_pending_rows: usize,
82 max_watermark_updates: usize,
83) -> Service {
84 Service::new().spawn_aborting(async move {
85 let mut poll = interval(config.collect_interval());
88 poll.set_missed_tick_behavior(MissedTickBehavior::Delay);
89
90 let checkpoint_lag_reporter = CheckpointLagMetricReporter::new_for_pipeline::<H>(
91 &metrics.collected_checkpoint_timestamp_lag,
92 &metrics.latest_collected_checkpoint_timestamp_lag_ms,
93 &metrics.latest_collected_checkpoint,
94 );
95
96 let mut pending: BTreeMap<u64, PendingCheckpoint<H>> = BTreeMap::new();
98 let mut pending_rows = 0;
99
100 info!(pipeline = H::NAME, "Starting collector");
101
102 let reader_lo_atomic = main_reader_lo.wait().await;
104
105 loop {
106 tokio::select! {
108 biased;
109
110 Some(mut indexed) = rx.recv(), if pending_rows < max_pending_rows => {
112 let reader_lo = reader_lo_atomic.load(Ordering::Relaxed);
113
114 metrics
115 .collector_reader_lo
116 .with_label_values(&[H::NAME])
117 .set(reader_lo as i64);
118
119 let mut recv_cps = 0usize;
120 let mut recv_rows = 0usize;
121 loop {
122 if indexed.checkpoint() < reader_lo {
123 indexed.values.clear();
124 metrics
125 .total_collector_skipped_checkpoints
126 .with_label_values(&[H::NAME])
127 .inc();
128 }
129
130 recv_cps += 1;
131 recv_rows += indexed.len();
132 pending_rows += indexed.len();
133 pending.insert(indexed.checkpoint(), indexed.into());
134
135 if pending_rows >= max_pending_rows {
136 break;
137 }
138
139 match rx.try_recv() {
140 Ok(next) => indexed = next,
141 Err(_) => break,
142 }
143 }
144
145 metrics
146 .total_collector_rows_received
147 .with_label_values(&[H::NAME])
148 .inc_by(recv_rows as u64);
149 metrics
150 .total_collector_checkpoints_received
151 .with_label_values(&[H::NAME])
152 .inc_by(recv_cps as u64);
153
154 if pending_rows < min_eager_rows {
155 continue;
156 }
157 }
158 _ = poll.tick() => {}
162 }
163
164 loop {
169 let guard = metrics
170 .collector_gather_latency
171 .with_label_values(&[H::NAME])
172 .start_timer();
173
174 let mut batch = H::Batch::default();
175 let mut watermark = Vec::new();
176 let mut batch_len = 0;
177
178 while let Some(mut entry) = pending.first_entry() {
179 if watermark.len() >= max_watermark_updates {
180 break;
181 }
182
183 let indexed = entry.get_mut();
184 let before = indexed.values.len();
185 let status = handler.batch(&mut batch, &mut indexed.values);
186 let taken = before - indexed.values.len();
187
188 batch_len += taken;
189 watermark.push(indexed.watermark.take(taken));
190 if indexed.is_empty() {
191 checkpoint_lag_reporter.report_lag(
192 indexed.watermark.checkpoint(),
193 indexed.watermark.timestamp_ms(),
194 );
195 entry.remove();
196 }
197
198 if status == BatchStatus::Ready {
199 break;
200 }
201 }
202
203 let elapsed = guard.stop_and_record();
204 debug!(
205 pipeline = H::NAME,
206 elapsed_ms = elapsed * 1000.0,
207 rows = batch_len,
208 "Gathered batch",
209 );
210
211 metrics
212 .total_collector_batches_created
213 .with_label_values(&[H::NAME])
214 .inc();
215
216 metrics
217 .collector_batch_size
218 .with_label_values(&[H::NAME])
219 .observe(batch_len as f64);
220
221 pending_rows -= batch_len;
222
223 let batched_rows = BatchedRows {
224 batch,
225 batch_len,
226 watermark,
227 };
228 if tx.send(batched_rows).await.is_err() {
229 info!(
230 pipeline = H::NAME,
231 "Committer closed channel, stopping collector"
232 );
233 return Ok(());
234 }
235
236 if pending.is_empty() {
237 break;
238 }
239 }
240
241 if rx.is_closed() && rx.is_empty() && pending_rows == 0 {
242 info!(
243 pipeline = H::NAME,
244 "Processor closed channel, pending rows empty, stopping collector",
245 );
246 break;
247 }
248 }
249
250 Ok(())
251 })
252}
253
254#[cfg(test)]
255mod tests {
256 use std::time::Duration;
257
258 use async_trait::async_trait;
259 use tokio::sync::mpsc;
260
261 use crate::metrics::tests::test_metrics;
262 use crate::mocks::store::FallibleMockConnection;
263 use crate::mocks::store::FallibleMockStore;
264 use crate::pipeline::Processor;
265 use crate::pipeline::concurrent::BatchStatus;
266 use crate::types::full_checkpoint_content::Checkpoint;
267
268 use super::*;
269
270 #[derive(Clone)]
271 struct Entry;
272
273 struct TestHandler;
274
275 const TEST_MAX_CHUNK_ROWS: usize = 1024;
277
278 #[async_trait]
279 impl Processor for TestHandler {
280 type Value = Entry;
281 const NAME: &'static str = "test_handler";
282
283 async fn process(&self, _checkpoint: &Arc<Checkpoint>) -> anyhow::Result<Vec<Self::Value>> {
284 Ok(vec![])
285 }
286 }
287
288 #[async_trait]
289 impl Handler for TestHandler {
290 type Store = FallibleMockStore;
291 type Batch = Vec<Entry>;
292
293 const MIN_EAGER_ROWS: usize = 10;
294 const MAX_PENDING_ROWS: usize = 10000;
295
296 fn batch(
297 &self,
298 batch: &mut Self::Batch,
299 values: &mut std::vec::IntoIter<Self::Value>,
300 ) -> BatchStatus {
301 let remaining_capacity = TEST_MAX_CHUNK_ROWS.saturating_sub(batch.len());
303 let to_take = remaining_capacity.min(values.len());
304 batch.extend(values.take(to_take));
305
306 if batch.len() >= TEST_MAX_CHUNK_ROWS {
307 BatchStatus::Ready
308 } else {
309 BatchStatus::Pending
310 }
311 }
312
313 async fn commit<'a>(
314 &self,
315 _batch: &Self::Batch,
316 _conn: &mut FallibleMockConnection<'a>,
317 ) -> anyhow::Result<usize> {
318 tokio::time::sleep(Duration::from_millis(1000)).await;
319 Ok(0)
320 }
321 }
322
323 async fn expect_timeout<H: Handler>(
325 rx: &mut mpsc::Receiver<BatchedRows<H>>,
326 duration: Duration,
327 ) {
328 match tokio::time::timeout(duration, rx.recv()).await {
329 Err(_) => (), Ok(_) => panic!("Expected timeout but received data instead"),
331 }
332 }
333
334 async fn recv_with_timeout<H: Handler>(
337 rx: &mut mpsc::Receiver<BatchedRows<H>>,
338 timeout: Duration,
339 ) -> BatchedRows<H> {
340 match tokio::time::timeout(timeout, rx.recv()).await {
341 Ok(Some(batch)) => batch,
342 Ok(None) => panic!("Collector channel was closed unexpectedly"),
343 Err(_) => panic!("Test timed out waiting for batch from collector"),
344 }
345 }
346
347 #[tokio::test]
348 async fn test_collector_batches_data() {
349 let (processor_tx, processor_rx) = mpsc::channel(10);
350 let (collector_tx, mut collector_rx) = mpsc::channel(10);
351 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
352
353 let handler = Arc::new(TestHandler);
354 let _collector = collector::<TestHandler>(
355 handler,
356 CommitterConfig::default(),
357 processor_rx,
358 collector_tx,
359 main_reader_lo.clone(),
360 test_metrics(),
361 TestHandler::MIN_EAGER_ROWS,
362 TestHandler::MAX_PENDING_ROWS,
363 TestHandler::MAX_WATERMARK_UPDATES,
364 );
365
366 let part1_length = TEST_MAX_CHUNK_ROWS / 2;
367 let part2_length = TEST_MAX_CHUNK_ROWS - part1_length - 1;
368
369 let test_data = vec![
371 IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry; part1_length]),
372 IndexedCheckpoint::new(0, 2, 20, 2000, vec![Entry; part2_length]),
373 IndexedCheckpoint::new(0, 3, 30, 3000, vec![Entry, Entry]),
374 ];
375
376 for data in test_data {
377 processor_tx.send(data).await.unwrap();
378 }
379
380 let batch1 = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
381 assert_eq!(batch1.batch_len, TEST_MAX_CHUNK_ROWS);
382
383 let batch2 = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
384 assert_eq!(batch2.batch_len, 1);
385 }
386
387 #[tokio::test]
388 async fn test_collector_shutdown() {
389 let (processor_tx, processor_rx) = mpsc::channel(10);
390 let (collector_tx, mut collector_rx) = mpsc::channel(10);
391 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
392
393 let handler = Arc::new(TestHandler);
394 let mut collector = collector::<TestHandler>(
395 handler,
396 CommitterConfig::default(),
397 processor_rx,
398 collector_tx,
399 main_reader_lo,
400 test_metrics(),
401 TestHandler::MIN_EAGER_ROWS,
402 TestHandler::MAX_PENDING_ROWS,
403 TestHandler::MAX_WATERMARK_UPDATES,
404 );
405
406 processor_tx
407 .send(IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry, Entry]))
408 .await
409 .unwrap();
410
411 tokio::time::sleep(Duration::from_millis(200)).await;
412
413 let batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
414 assert_eq!(batch.batch_len, 2);
415
416 drop(processor_tx);
418
419 tokio::time::timeout(Duration::from_millis(500), collector.join())
421 .await
422 .expect("collector shutdown timeout")
423 .expect("collector shutdown failed");
424 }
425
426 #[tokio::test]
427 async fn test_collector_respects_max_pending() {
428 let processor_channel_size = 5; let collector_channel_size = 2; let (processor_tx, processor_rx) = mpsc::channel(processor_channel_size);
431 let (collector_tx, _collector_rx) = mpsc::channel(collector_channel_size);
432 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
433
434 let metrics = test_metrics();
435
436 let handler = Arc::new(TestHandler);
437 let _collector = collector::<TestHandler>(
438 handler,
439 CommitterConfig::default(),
440 processor_rx,
441 collector_tx,
442 main_reader_lo.clone(),
443 metrics.clone(),
444 TestHandler::MIN_EAGER_ROWS,
445 TestHandler::MAX_PENDING_ROWS,
446 TestHandler::MAX_WATERMARK_UPDATES,
447 );
448
449 let data = IndexedCheckpoint::new(
451 0,
452 1,
453 10,
454 1000,
455 vec![
456 Entry;
457 TestHandler::MAX_PENDING_ROWS
459 + TEST_MAX_CHUNK_ROWS * collector_channel_size
460 ],
461 );
462 processor_tx.send(data).await.unwrap();
463
464 tokio::time::sleep(Duration::from_millis(200)).await;
465
466 for _ in 0..processor_channel_size {
468 let more_data = IndexedCheckpoint::new(0, 2, 11, 1000, vec![Entry]);
469 processor_tx.send(more_data).await.unwrap();
470 }
471
472 let even_more_data = IndexedCheckpoint::new(0, 3, 12, 1000, vec![Entry]);
474
475 let send_result = processor_tx.try_send(even_more_data);
476 assert!(matches!(
477 send_result,
478 Err(mpsc::error::TrySendError::Full(_))
479 ));
480 }
481
482 #[tokio::test]
483 async fn test_collector_accumulates_across_checkpoints_until_eager_threshold() {
484 let (processor_tx, processor_rx) = mpsc::channel(10);
485 let (collector_tx, mut collector_rx) = mpsc::channel(10);
486 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
487
488 let config = CommitterConfig {
490 collect_interval_ms: 60_000,
491 ..CommitterConfig::default()
492 };
493 let handler = Arc::new(TestHandler);
494 let _collector = collector::<TestHandler>(
495 handler,
496 config,
497 processor_rx,
498 collector_tx,
499 main_reader_lo.clone(),
500 test_metrics(),
501 TestHandler::MIN_EAGER_ROWS,
502 TestHandler::MAX_PENDING_ROWS,
503 TestHandler::MAX_WATERMARK_UPDATES,
504 );
505
506 let start_time = std::time::Instant::now();
507
508 let initial_batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
510 assert_eq!(initial_batch.batch_len, 0);
511
512 let below_threshold =
514 IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry; TestHandler::MIN_EAGER_ROWS - 1]);
515 processor_tx.send(below_threshold).await.unwrap();
516
517 expect_timeout(&mut collector_rx, Duration::from_secs(1)).await;
519
520 let threshold_trigger = IndexedCheckpoint::new(
522 0,
523 2,
524 20,
525 2000,
526 vec![Entry; 1], );
528 processor_tx.send(threshold_trigger).await.unwrap();
529
530 let eager_batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
532 assert_eq!(eager_batch.batch_len, TestHandler::MIN_EAGER_ROWS);
533
534 let elapsed = start_time.elapsed();
536 assert!(elapsed < Duration::from_secs(10));
537 }
538
539 #[tokio::test]
540 async fn test_immediate_batch_on_min_eager_rows() {
541 let (processor_tx, processor_rx) = mpsc::channel(10);
542 let (collector_tx, mut collector_rx) = mpsc::channel(10);
543 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
544
545 let config = CommitterConfig {
547 collect_interval_ms: 60_000,
548 ..CommitterConfig::default()
549 };
550 let handler = Arc::new(TestHandler);
551 let _collector = collector::<TestHandler>(
552 handler,
553 config,
554 processor_rx,
555 collector_tx,
556 main_reader_lo.clone(),
557 test_metrics(),
558 TestHandler::MIN_EAGER_ROWS,
559 TestHandler::MAX_PENDING_ROWS,
560 TestHandler::MAX_WATERMARK_UPDATES,
561 );
562
563 let initial_batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
565 assert_eq!(initial_batch.batch_len, 0);
566 expect_timeout(&mut collector_rx, Duration::from_secs(1)).await;
568
569 let start_time = std::time::Instant::now();
570
571 let exact_threshold =
573 IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry; TestHandler::MIN_EAGER_ROWS]);
574 processor_tx.send(exact_threshold).await.unwrap();
575
576 let batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
578 assert_eq!(batch.batch_len, TestHandler::MIN_EAGER_ROWS);
579
580 let elapsed = start_time.elapsed();
582 assert!(elapsed < Duration::from_secs(10));
583 }
584
585 #[tokio::test]
586 async fn test_collector_waits_for_timer_when_below_eager_threshold() {
587 let (processor_tx, processor_rx) = mpsc::channel(10);
588 let (collector_tx, mut collector_rx) = mpsc::channel(10);
589 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
590
591 let config = CommitterConfig {
593 collect_interval_ms: 3000,
594 ..CommitterConfig::default()
595 };
596 let handler = Arc::new(TestHandler);
597 let _collector = collector::<TestHandler>(
598 handler,
599 config,
600 processor_rx,
601 collector_tx,
602 main_reader_lo.clone(),
603 test_metrics(),
604 TestHandler::MIN_EAGER_ROWS,
605 TestHandler::MAX_PENDING_ROWS,
606 TestHandler::MAX_WATERMARK_UPDATES,
607 );
608
609 let initial_batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(1)).await;
611 assert_eq!(initial_batch.batch_len, 0);
612
613 let below_threshold =
615 IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry; TestHandler::MIN_EAGER_ROWS - 1]);
616 processor_tx.send(below_threshold).await.unwrap();
617
618 expect_timeout(&mut collector_rx, Duration::from_secs(1)).await;
620
621 let timer_batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(4)).await;
623 assert_eq!(timer_batch.batch_len, TestHandler::MIN_EAGER_ROWS - 1);
624 }
625
626 #[tokio::test(start_paused = true)]
629 async fn test_collector_waits_for_main_reader_lo_init() {
630 let (processor_tx, processor_rx) = mpsc::channel(10);
631 let (collector_tx, mut collector_rx) = mpsc::channel(10);
632 let main_reader_lo = Arc::new(SetOnce::new());
633
634 let handler = Arc::new(TestHandler);
635 let collector = collector(
636 handler,
637 CommitterConfig {
638 collect_interval_ms: 200_000,
641 ..CommitterConfig::default()
642 },
643 processor_rx,
644 collector_tx,
645 main_reader_lo.clone(),
646 test_metrics(),
647 TestHandler::MIN_EAGER_ROWS,
648 TestHandler::MAX_PENDING_ROWS,
649 TestHandler::MAX_WATERMARK_UPDATES,
650 );
651
652 let test_data =
654 IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry; TestHandler::MIN_EAGER_ROWS + 1]);
655 processor_tx.send(test_data).await.unwrap();
656
657 tokio::time::advance(Duration::from_secs(100)).await;
660
661 assert!(collector_rx.try_recv().is_err());
662
663 main_reader_lo.set(AtomicU64::new(0)).ok();
665
666 tokio::time::advance(Duration::from_secs(1)).await;
667
668 let batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(2)).await;
669
670 assert_eq!(batch.batch_len, TestHandler::MIN_EAGER_ROWS + 1);
671
672 collector.shutdown().await.unwrap();
673 }
674
675 #[tokio::test]
678 async fn test_collector_drops_checkpoints_immediately_if_le_main_reader_lo() {
679 let (processor_tx, processor_rx) = mpsc::channel(10);
680 let (collector_tx, mut collector_rx) = mpsc::channel(10);
681 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(5))));
682 let metrics = test_metrics();
683
684 let collector = collector(
685 Arc::new(TestHandler),
686 CommitterConfig {
687 collect_interval_ms: 200_000,
690 ..CommitterConfig::default()
691 },
692 processor_rx,
693 collector_tx,
694 main_reader_lo.clone(),
695 metrics.clone(),
696 TestHandler::MIN_EAGER_ROWS,
697 TestHandler::MAX_PENDING_ROWS,
698 TestHandler::MAX_WATERMARK_UPDATES,
699 );
700
701 let eager_rows_plus_one = TestHandler::MIN_EAGER_ROWS + 1;
702
703 let test_data: Vec<_> = [1, 5, 2, 6, 4, 3]
704 .into_iter()
705 .map(|cp| IndexedCheckpoint::new(0, cp, 10, 1000, vec![Entry; eager_rows_plus_one]))
706 .collect();
707 for data in test_data {
708 processor_tx.send(data).await.unwrap();
709 }
710 let batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(2)).await;
711
712 assert_eq!(batch.watermark.len(), 6);
714 assert_eq!(
716 metrics
717 .total_collector_checkpoints_received
718 .with_label_values(&[TestHandler::NAME])
719 .get(),
720 6
721 );
722 assert_eq!(
724 metrics
725 .total_collector_skipped_checkpoints
726 .with_label_values(&[TestHandler::NAME])
727 .get(),
728 4
729 );
730 assert_eq!(batch.batch_len, eager_rows_plus_one * 2);
732
733 collector.shutdown().await.unwrap();
734 }
735
736 #[tokio::test(start_paused = true)]
741 async fn test_collector_only_filters_whole_checkpoints() {
742 let (processor_tx, processor_rx) = mpsc::channel(10);
743 let (collector_tx, mut collector_rx) = mpsc::channel(10);
744 let main_reader_lo = Arc::new(SetOnce::new_with(Some(AtomicU64::new(0))));
745
746 let metrics = test_metrics();
747
748 let collector = collector(
749 Arc::new(TestHandler),
750 CommitterConfig::default(),
751 processor_rx,
752 collector_tx,
753 main_reader_lo.clone(),
754 metrics.clone(),
755 TestHandler::MIN_EAGER_ROWS,
756 TestHandler::MAX_PENDING_ROWS,
757 TestHandler::MAX_WATERMARK_UPDATES,
758 );
759
760 let more_than_max_chunk_rows = TEST_MAX_CHUNK_ROWS + 10;
761
762 let test_data =
763 IndexedCheckpoint::new(0, 1, 10, 1000, vec![Entry; more_than_max_chunk_rows]);
764 processor_tx.send(test_data).await.unwrap();
765 tokio::time::advance(Duration::from_secs(1)).await;
766 let batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(2)).await;
767
768 assert_eq!(batch.batch_len, TEST_MAX_CHUNK_ROWS);
770
771 let test_data: Vec<_> = (2..=5)
773 .map(|cp| {
774 IndexedCheckpoint::new(
775 0,
776 cp,
777 10,
778 1000,
779 vec![Entry; TestHandler::MIN_EAGER_ROWS + 1],
780 )
781 })
782 .collect();
783 for data in test_data {
784 processor_tx.send(data).await.unwrap();
785 }
786 let atomic = main_reader_lo.get().unwrap();
787 atomic.store(4, Ordering::Relaxed);
788 tokio::time::advance(Duration::from_secs(10)).await;
789
790 let batch = recv_with_timeout(&mut collector_rx, Duration::from_secs(2)).await;
791
792 assert_eq!(batch.batch_len, 10);
794 assert_eq!(batch.watermark[0].watermark.checkpoint_hi_inclusive, 1);
795
796 recv_with_timeout(&mut collector_rx, Duration::from_secs(2)).await;
797
798 assert_eq!(
799 metrics
800 .total_collector_skipped_checkpoints
801 .with_label_values(&[TestHandler::NAME])
802 .get(),
803 2
804 );
805 assert_eq!(
806 metrics
807 .total_collector_checkpoints_received
808 .with_label_values(&[TestHandler::NAME])
809 .get(),
810 5
811 );
812
813 collector.shutdown().await.unwrap();
814 }
815}