1use std::{
4 collections::BinaryHeap,
5 future::Future,
6 pin::Pin,
7 task::{Context, Poll},
8 time::{Duration, Instant},
9};
10
11use futures_time::future::Timer;
12use tracing::instrument;
13
14use crate::{errors::SessionError, protocol::FrameId};
15
16#[derive(Clone, Copy, Debug)]
21struct Buffered<T> {
22 item: T,
23 buffered_at: Instant,
24}
25
26impl<T: PartialEq> PartialEq for Buffered<T> {
27 fn eq(&self, other: &Self) -> bool {
28 self.item.eq(&other.item)
29 }
30}
31
32impl<T: Eq> Eq for Buffered<T> {}
33
34impl<T: PartialOrd> PartialOrd for Buffered<T> {
35 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
36 self.item.partial_cmp(&other.item)
37 }
38}
39
40impl<T: Ord> Ord for Buffered<T> {
41 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
42 self.item.cmp(&other.item)
43 }
44}
45
46impl<T: PartialOrd<FrameId>> PartialEq<FrameId> for Buffered<T> {
47 fn eq(&self, other: &FrameId) -> bool {
48 self.item.partial_cmp(other) == Some(std::cmp::Ordering::Equal)
49 }
50}
51
52impl<T: PartialOrd<FrameId>> PartialOrd<FrameId> for Buffered<T> {
53 fn partial_cmp(&self, other: &FrameId) -> Option<std::cmp::Ordering> {
54 self.item.partial_cmp(other)
55 }
56}
57
58#[must_use = "streams do nothing unless polled"]
82#[pin_project::pin_project]
83pub struct Sequencer<S: futures::Stream> {
84 #[pin]
85 inner: S,
86 #[pin]
87 timer: futures_time::task::Sleep,
88 buffer: BinaryHeap<std::cmp::Reverse<Buffered<S::Item>>>,
89 next_id: FrameId,
90 last_emitted: Instant,
91 max_wait: Duration,
92 max_item_age: Option<Duration>,
95 max_frames_behind_gap: Option<usize>,
98 state: State,
99}
100
101impl<S> Sequencer<S>
102where
103 S: futures::Stream,
104 S::Item: Ord + PartialOrd<FrameId>,
105{
106 fn new(
111 inner: S,
112 max_wait: Duration,
113 capacity: usize,
114 max_item_age: Option<Duration>,
115 max_frames_behind_gap: Option<usize>,
116 ) -> Self {
117 assert!(capacity > 0, "capacity should be positive");
118 Self {
119 inner,
120 buffer: BinaryHeap::with_capacity(capacity),
121 timer: futures_time::task::sleep(max_wait.max(Duration::from_millis(1)).into()),
122 next_id: 1,
123 last_emitted: Instant::now(),
124 max_wait,
125 max_item_age: max_item_age.filter(|age| !age.is_zero()),
126 max_frames_behind_gap: max_frames_behind_gap.map(|n| n.max(1)),
130 state: State::Polling,
131 }
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136enum State {
137 Polling,
138 BufferUpdated,
139 Done,
140}
141
142impl<S> futures::Stream for Sequencer<S>
143where
144 S: futures::Stream,
145 S::Item: Ord + PartialOrd<FrameId>,
146{
147 type Item = Result<S::Item, SessionError>;
148
149 #[instrument(name = "Sequencer::poll_next", level = "trace", skip(self, cx), fields(next_frame_id = self.next_id, state = ?self.state))]
150 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
151 let mut this = self.project();
152 if *this.next_id == 0 {
153 tracing::debug!("end of frame sequence reached");
154 return Poll::Ready(None);
155 }
156
157 loop {
158 match *this.state {
159 State::Polling => {
160 if this.buffer.len() < this.buffer.capacity() {
161 let stream_poll = this.inner.as_mut().poll_next(cx);
163
164 let timer_poll = if !this.buffer.is_empty() {
166 let poll = this.timer.as_mut().poll(cx);
167 if poll.is_ready() {
168 this.timer.as_mut().reset_timer();
169 }
170 poll
171 } else {
172 Poll::Pending
173 };
174
175 match (stream_poll, timer_poll) {
176 (Poll::Pending, Poll::Pending) => {
177 tracing::trace!("pending");
178 *this.state = State::Polling;
179 return Poll::Pending;
180 }
181 (Poll::Ready(Some(item)), _) => {
182 if this.buffer.is_empty() {
185 *this.last_emitted = Instant::now();
186 }
187
188 if item.lt(this.next_id) {
189 tracing::error!("old item");
191 *this.state = State::Polling;
192 } else {
193 tracing::trace!("new item");
195 this.buffer.push(std::cmp::Reverse(Buffered {
196 item,
197 buffered_at: Instant::now(),
198 }));
199 *this.state = State::BufferUpdated;
200 }
201 }
202 (Poll::Ready(None), _) => {
203 tracing::trace!(len = this.buffer.len(), "stream is done");
204 *this.state = State::Done
205 }
206 (_, Poll::Ready(_)) => {
207 tracing::trace!("timer elapsed");
209 *this.state = State::BufferUpdated;
210 }
211 }
212 } else {
213 tracing::warn!("sequencer buffer is full");
215 *this.state = State::BufferUpdated;
216 }
217 }
218 State::BufferUpdated => {
219 if let Some(next) = this.buffer.peek().map(|item| &item.0) {
221 if next.eq(this.next_id) {
222 let stale = this
223 .max_item_age
224 .is_some_and(|max_age| next.buffered_at.elapsed() >= max_age);
225
226 *this.next_id = this.next_id.wrapping_add(1);
227 *this.last_emitted = Instant::now();
228 *this.state = State::BufferUpdated;
229
230 if stale {
235 let discarded = this.next_id.wrapping_sub(1);
236 this.buffer.pop();
237 tracing::trace!(discarded, "discard frame that exceeded max age");
238 return Poll::Ready(Some(Err(SessionError::FrameDiscarded(discarded))));
239 }
240
241 tracing::trace!("emit next frame");
242
243 return Poll::Ready(this.buffer.pop().map(|item| Ok(item.0.item)));
244 } else if this.last_emitted.elapsed() >= *this.max_wait
245 || this.buffer.len() == this.buffer.capacity()
246 || this.max_frames_behind_gap.is_some_and(|n| {
251 this.buffer.len() >= n
258 || next.gt(&this.next_id.saturating_add(
261 FrameId::try_from(n).unwrap_or(FrameId::MAX).saturating_sub(1),
262 ))
263 })
264 {
265 let discarded = *this.next_id;
266 *this.next_id = this.next_id.wrapping_add(1);
267 *this.state = State::BufferUpdated;
273
274 tracing::trace!(discarded, "discard frame");
275
276 return Poll::Ready(Some(Err(SessionError::FrameDiscarded(discarded))));
277 }
278 } else {
279 tracing::trace!("buffer is empty");
280 }
281
282 *this.state = State::Polling;
284 }
285 State::Done => {
286 return if let Some(next) = this.buffer.peek().map(|item| &item.0) {
288 if next.lt(this.next_id) {
289 tracing::error!("old item");
290 this.buffer.pop();
291 continue;
292 } else if next.eq(this.next_id) {
293 *this.next_id = this.next_id.wrapping_add(1);
294 tracing::trace!("emit next frame when done");
295
296 Poll::Ready(this.buffer.pop().map(|item| Ok(item.0.item)))
297 } else {
298 let discarded = *this.next_id;
299 *this.next_id = this.next_id.wrapping_add(1);
300 tracing::trace!(discarded, "discard frame when done");
301
302 Poll::Ready(Some(Err(SessionError::FrameDiscarded(discarded))))
303 }
304 } else {
305 tracing::trace!("buffer is empty and done");
306 Poll::Ready(None)
307 };
308 }
309 }
310 }
311 }
312}
313
314#[derive(Clone, Copy, Debug)]
320pub struct SequencerConfig {
321 pub max_wait: Duration,
323 pub capacity: usize,
325 pub max_item_age: Option<Duration>,
327 pub max_frames_behind_gap: Option<usize>,
338}
339
340pub trait SequencerExt: futures::Stream {
342 fn sequencer(self, timeout: Duration, capacity: usize) -> Sequencer<Self>
345 where
346 Self::Item: Ord + PartialOrd<FrameId>,
347 Self: Sized,
348 {
349 Sequencer::new(self, timeout, capacity, None, None)
350 }
351
352 fn sequencer_with_max_age(
355 self,
356 timeout: Duration,
357 capacity: usize,
358 max_item_age: Option<Duration>,
359 ) -> Sequencer<Self>
360 where
361 Self::Item: Ord + PartialOrd<FrameId>,
362 Self: Sized,
363 {
364 Sequencer::new(self, timeout, capacity, max_item_age, None)
365 }
366
367 fn sequencer_with(self, cfg: SequencerConfig) -> Sequencer<Self>
369 where
370 Self::Item: Ord + PartialOrd<FrameId>,
371 Self: Sized,
372 {
373 Sequencer::new(
374 self,
375 cfg.max_wait,
376 cfg.capacity,
377 cfg.max_item_age,
378 cfg.max_frames_behind_gap,
379 )
380 }
381}
382
383impl<T: ?Sized> SequencerExt for T where T: futures::Stream {}
384
385#[cfg(test)]
386mod tests {
387 use futures::{SinkExt, StreamExt, TryStreamExt, pin_mut};
388 use futures_time::future::FutureExt;
389
390 use super::*;
391
392 #[test_log::test(tokio::test)]
393 async fn sequencer_should_return_entries_in_order() -> anyhow::Result<()> {
394 let mut expected = vec![4u32, 1, 5, 7, 8, 6, 2, 3];
395
396 let actual: Vec<u32> = futures::stream::iter(expected.clone())
397 .sequencer(Duration::from_secs(5), 4096)
398 .try_collect()
399 .timeout(futures_time::time::Duration::from_secs(5))
400 .await??;
401
402 expected.sort();
403 assert_eq!(expected, actual);
404
405 Ok(())
406 }
407
408 #[test_log::test(tokio::test)]
409 async fn sequencer_should_discard_entries_that_exceeded_the_max_age() -> anyhow::Result<()> {
410 let (seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
411
412 let seq_stream =
414 seq_stream.sequencer_with_max_age(Duration::from_secs(30), 4096, Some(Duration::from_millis(100)));
415
416 pin_mut!(seq_sink);
417 pin_mut!(seq_stream);
418
419 seq_sink.send(2u32).await?;
421
422 assert!(
425 seq_stream
426 .try_next()
427 .timeout(futures_time::time::Duration::from_millis(50))
428 .await
429 .is_err(),
430 "nothing is emitted while frame 1 is missing"
431 );
432
433 tokio::time::sleep(std::time::Duration::from_millis(250)).await;
434
435 seq_sink.send(1u32).await?;
438
439 assert_eq!(Some(1), seq_stream.try_next().await?, "the fresh frame is delivered");
440 assert!(
441 matches!(seq_stream.try_next().await, Err(SessionError::FrameDiscarded(2))),
442 "the stale frame must be discarded, not delivered late"
443 );
444
445 Ok(())
446 }
447
448 #[test_log::test(tokio::test)]
449 async fn sequencer_should_deliver_entries_within_the_max_age() -> anyhow::Result<()> {
450 let (seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
451
452 let seq_stream =
453 seq_stream.sequencer_with_max_age(Duration::from_secs(30), 4096, Some(Duration::from_secs(30)));
454
455 pin_mut!(seq_sink);
456 pin_mut!(seq_stream);
457
458 seq_sink.send(2u32).await?;
461 assert!(
462 seq_stream
463 .try_next()
464 .timeout(futures_time::time::Duration::from_millis(50))
465 .await
466 .is_err()
467 );
468 tokio::time::sleep(std::time::Duration::from_millis(250)).await;
469 seq_sink.send(1u32).await?;
470
471 assert_eq!(Some(1), seq_stream.try_next().await?);
472 assert_eq!(Some(2), seq_stream.try_next().await?);
473
474 Ok(())
475 }
476
477 #[test_log::test(tokio::test)]
478 async fn sequencer_should_not_allow_emitted_entries() -> anyhow::Result<()> {
479 let (seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
480
481 let seq_stream = seq_stream.sequencer(Duration::from_secs(1), 4096);
482
483 pin_mut!(seq_sink);
484 pin_mut!(seq_stream);
485
486 seq_sink.send(1u32).await?;
487 assert_eq!(Some(1), seq_stream.try_next().await?);
488
489 seq_sink.send(2u32).await?;
490 assert_eq!(Some(2), seq_stream.try_next().await?);
491
492 seq_sink.send(2u32).await?;
493 seq_sink.send(1u32).await?;
494
495 seq_sink.send(3u32).await?;
496 assert_eq!(Some(3), seq_stream.try_next().await?);
497
498 Ok(())
499 }
500
501 #[test_log::test(tokio::test)]
509 async fn sequencer_should_abandon_a_gap_once_enough_later_frames_are_waiting() -> anyhow::Result<()> {
510 let max_wait = Duration::from_secs(5);
513 let (mut seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
514
515 for v in [2u32, 3, 4] {
517 seq_sink.feed(v).await?;
518 }
519 seq_sink.flush().await?;
520
521 let seq_stream = seq_stream.sequencer_with(SequencerConfig {
522 max_wait,
523 capacity: 4096,
524 max_item_age: None,
525 max_frames_behind_gap: Some(2),
526 });
527 pin_mut!(seq_stream);
528
529 let released: Vec<u32> = tokio::time::timeout(max_wait / 2, async {
532 assert!(
533 matches!(seq_stream.try_next().await, Err(SessionError::FrameDiscarded(1))),
534 "the gap must be reported as loss, the signal the consumer already handles"
535 );
536 seq_stream.by_ref().take(3).try_collect().await
537 })
538 .await
539 .map_err(|_| anyhow::anyhow!("frames already received waited on a frame that is never coming"))??;
540
541 assert_eq!(vec![2, 3, 4], released, "everything behind the gap must follow it out");
542
543 drop(seq_sink);
546 Ok(())
547 }
548
549 #[test_log::test(tokio::test)]
559 async fn sequencer_should_abandon_a_gap_when_the_sequence_has_advanced_past_it() -> anyhow::Result<()> {
560 let max_wait = Duration::from_secs(5);
561 let (mut seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
562
563 seq_sink.feed(40u32).await?;
566 seq_sink.flush().await?;
567
568 let seq_stream = seq_stream.sequencer_with(SequencerConfig {
569 max_wait,
570 capacity: 4096,
571 max_item_age: None,
572 max_frames_behind_gap: Some(4),
573 });
574 pin_mut!(seq_stream);
575
576 let now = Instant::now();
581 for expected_gap in 1..=36u32 {
582 assert!(
583 matches!(
584 seq_stream.try_next().await,
585 Err(SessionError::FrameDiscarded(id)) if id == expected_gap
586 ),
587 "frame {expected_gap} is behind the advanced sequence and must be given up on"
588 );
589 }
590 assert!(
591 now.elapsed() < max_wait / 2,
592 "a single frame far ahead is evidence enough, with no frames buffered behind the gap to count; took {:?}",
593 now.elapsed()
594 );
595
596 drop(seq_sink);
597 Ok(())
598 }
599
600 #[test_log::test(tokio::test)]
604 async fn sequencer_should_keep_waiting_while_fewer_frames_are_behind_the_gap() -> anyhow::Result<()> {
605 let max_wait = Duration::from_millis(300);
606 let (mut seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
607
608 seq_sink.feed(2u32).await?;
610 seq_sink.flush().await?;
611
612 let seq_stream = seq_stream.sequencer_with(SequencerConfig {
613 max_wait,
614 capacity: 4096,
615 max_item_age: None,
616 max_frames_behind_gap: Some(3),
617 });
618 pin_mut!(seq_stream);
619
620 let now = Instant::now();
621 assert!(matches!(
622 seq_stream.try_next().await,
623 Err(SessionError::FrameDiscarded(1))
624 ));
625 assert!(
626 now.elapsed() >= max_wait,
627 "under the threshold the timeout still governs; took {:?}",
628 now.elapsed()
629 );
630
631 drop(seq_sink);
632 Ok(())
633 }
634
635 #[test_log::test(tokio::test)]
638 async fn sequencer_without_the_gap_bound_should_still_wait_for_the_timeout() -> anyhow::Result<()> {
639 let max_wait = Duration::from_millis(300);
640 let (mut seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
641
642 for v in [2u32, 3, 4, 5, 6] {
643 seq_sink.feed(v).await?;
644 }
645 seq_sink.flush().await?;
646
647 let seq_stream = seq_stream.sequencer_with(SequencerConfig {
648 max_wait,
649 capacity: 4096,
650 max_item_age: None,
651 max_frames_behind_gap: None,
652 });
653 pin_mut!(seq_stream);
654
655 let now = Instant::now();
656 assert!(matches!(
657 seq_stream.try_next().await,
658 Err(SessionError::FrameDiscarded(1))
659 ));
660 assert!(
661 now.elapsed() >= max_wait,
662 "with no gap bound the timeout is the only rule; took {:?}",
663 now.elapsed()
664 );
665
666 drop(seq_sink);
667 Ok(())
668 }
669
670 #[test_log::test(tokio::test)]
671 async fn sequencer_should_discard_entry_on_timeout() -> anyhow::Result<()> {
672 let timeout = Duration::from_millis(25);
673 let (mut seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
674
675 let input = vec![2u32, 1, 4, 5, 8, 7, 9, 11, 10];
676
677 let input_clone = input.clone();
678 let jh = hopr_utils::runtime::prelude::spawn(async move {
679 for v in input_clone {
680 seq_sink
681 .feed(v)
682 .delay(futures_time::time::Duration::from_millis(5))
683 .await?;
684 }
685 seq_sink.flush().await?;
686 seq_sink.close().await
687 });
688
689 let seq_stream = seq_stream.sequencer(timeout, 4096);
690
691 pin_mut!(seq_stream);
692
693 assert_eq!(Some(1), seq_stream.try_next().await?);
694 assert_eq!(Some(2), seq_stream.try_next().await?);
695
696 let now = Instant::now();
697 assert!(matches!(
698 seq_stream.try_next().await,
699 Err(SessionError::FrameDiscarded(3))
700 ));
701 assert!(now.elapsed() >= timeout);
702
703 assert_eq!(Some(4), seq_stream.try_next().await?);
704 assert_eq!(Some(5), seq_stream.try_next().await?);
705
706 assert!(matches!(
707 seq_stream.try_next().await,
708 Err(SessionError::FrameDiscarded(6))
709 ));
710
711 assert_eq!(Some(7), seq_stream.try_next().await?);
712 assert_eq!(Some(8), seq_stream.try_next().await?);
713 assert_eq!(Some(9), seq_stream.try_next().await?);
714 assert_eq!(Some(10), seq_stream.try_next().await?);
715 assert_eq!(Some(11), seq_stream.try_next().await?);
716
717 assert_eq!(None, seq_stream.try_next().await?);
718
719 let _ = jh.await?;
720 Ok(())
721 }
722
723 #[test_log::test(tokio::test)]
724 async fn sequencer_should_discard_entry_close() -> anyhow::Result<()> {
725 let (seq_sink, seq_stream) = futures::channel::mpsc::unbounded();
726
727 let input = vec![2u32, 1, 3, 5, 4, 8, 11];
728
729 hopr_utils::runtime::prelude::spawn(futures::stream::iter(input.clone()).map(Ok).forward(seq_sink)).await??;
730
731 let seq_stream = seq_stream.sequencer(Duration::from_millis(25), 4096);
732
733 pin_mut!(seq_stream);
734
735 assert_eq!(Some(1), seq_stream.try_next().await?);
736 assert_eq!(Some(2), seq_stream.try_next().await?);
737 assert_eq!(Some(3), seq_stream.try_next().await?);
738 assert_eq!(Some(4), seq_stream.try_next().await?);
739 assert_eq!(Some(5), seq_stream.try_next().await?);
740 assert!(matches!(
741 seq_stream.try_next().await,
742 Err(SessionError::FrameDiscarded(6))
743 ));
744 assert!(matches!(
745 seq_stream.try_next().await,
746 Err(SessionError::FrameDiscarded(7))
747 ));
748 assert_eq!(Some(8), seq_stream.try_next().await?);
749 assert!(matches!(
750 seq_stream.try_next().await,
751 Err(SessionError::FrameDiscarded(9))
752 ));
753 assert!(matches!(
754 seq_stream.try_next().await,
755 Err(SessionError::FrameDiscarded(10))
756 ));
757 assert_eq!(Some(11), seq_stream.try_next().await?);
758 assert_eq!(None, seq_stream.try_next().await?);
759
760 Ok(())
761 }
762
763 #[test_log::test(tokio::test)]
764 async fn sequencer_should_discard_entry_when_inner_stream_pending() -> anyhow::Result<()> {
765 let sent = vec![4u32, 1, 7, 8, 6, 2, 3];
766 let (tx, rx) = futures::channel::mpsc::unbounded();
767
768 pin_mut!(tx);
769 tx.send_all(&mut futures::stream::iter(sent.clone()).map(Ok)).await?;
770
771 let rx = rx.sequencer(Duration::from_millis(10), 4096);
772 pin_mut!(rx);
773
774 assert!(matches!(rx.next().await, Some(Ok(1))));
775 assert!(matches!(rx.next().await, Some(Ok(2))));
776 assert!(matches!(rx.next().await, Some(Ok(3))));
777 assert!(matches!(rx.next().await, Some(Ok(4))));
778 assert!(matches!(rx.next().await, Some(Err(SessionError::FrameDiscarded(5)))));
779 assert!(matches!(rx.next().await, Some(Ok(6))));
780 assert!(matches!(rx.next().await, Some(Ok(7))));
781 assert!(matches!(rx.next().await, Some(Ok(8))));
782
783 Ok(())
784 }
785
786 #[test_log::test(tokio::test)]
787 async fn sequencer_should_discard_entry_when_capacity_is_reached() -> anyhow::Result<()> {
788 let sent = vec![4u32, 5, 7, 8, 2, 6, 3];
789 let (tx, rx) = futures::channel::mpsc::unbounded();
790
791 pin_mut!(tx);
792 tx.send_all(&mut futures::stream::iter(sent.clone()).map(Ok)).await?;
793
794 let rx = rx.sequencer(Duration::from_millis(10), 4);
795 pin_mut!(rx);
796
797 assert!(matches!(rx.next().await, Some(Err(SessionError::FrameDiscarded(1)))));
798 assert!(matches!(rx.next().await, Some(Err(SessionError::FrameDiscarded(2)))));
799 assert!(matches!(rx.next().await, Some(Err(SessionError::FrameDiscarded(3)))));
800 assert!(matches!(rx.next().await, Some(Ok(4))));
801 assert!(matches!(rx.next().await, Some(Ok(5))));
802 assert!(matches!(rx.next().await, Some(Ok(6))));
803 assert!(matches!(rx.next().await, Some(Ok(7))));
804 assert!(matches!(rx.next().await, Some(Ok(8))));
805
806 Ok(())
807 }
808
809 #[test_log::test(tokio::test)]
810 async fn sequencer_should_drain_contiguous_gap_within_single_timeout_window() -> anyhow::Result<()> {
811 let timeout = Duration::from_millis(50);
812 let (tx, rx) = futures::channel::mpsc::unbounded();
813
814 pin_mut!(tx);
815 tx.send_all(&mut futures::stream::iter([1u32, 2, 10, 11, 12]).map(Ok))
816 .await?;
817
818 let rx = rx.sequencer(timeout, 4096);
819 pin_mut!(rx);
820
821 assert_eq!(Some(1), rx.try_next().await?);
822 assert_eq!(Some(2), rx.try_next().await?);
823
824 let now = Instant::now();
825 for expected in 3u32..=9 {
826 assert!(matches!(
827 rx.next().await,
828 Some(Err(SessionError::FrameDiscarded(id))) if id == expected
829 ));
830 }
831 assert_eq!(Some(10), rx.try_next().await?);
832 assert_eq!(Some(11), rx.try_next().await?);
833 assert_eq!(Some(12), rx.try_next().await?);
834
835 assert!(
838 now.elapsed() < 3 * timeout,
839 "gap drain took {:?}, expected well under {:?}",
840 now.elapsed(),
841 7 * timeout
842 );
843
844 Ok(())
845 }
846
847 #[test_log::test(tokio::test)]
848 async fn sequencer_must_terminate_on_last_frame_id() -> anyhow::Result<()> {
849 let (tx, rx) = futures::channel::mpsc::unbounded();
850
851 pin_mut!(tx);
852 tx.send_all(&mut futures::stream::iter([FrameId::MAX - 1, FrameId::MAX, 1, 2]).map(Ok))
853 .await?;
854
855 let mut rx = rx.sequencer(Duration::from_millis(10), 1024);
856 rx.next_id = FrameId::MAX - 1;
857 pin_mut!(rx);
858
859 const LAST_ID: FrameId = FrameId::MAX - 1;
860 assert!(matches!(rx.next().await, Some(Ok(LAST_ID))));
861 assert!(matches!(rx.next().await, Some(Ok(FrameId::MAX))));
862 assert!(rx.next().await.is_none());
863
864 Ok(())
865 }
866
867 #[test_log::test(tokio::test(flavor = "multi_thread"))]
868 async fn sequencer_must_not_discard_frames_when_buffer_was_empty_after_timeout() -> anyhow::Result<()> {
869 let (tx, rx) = futures::channel::mpsc::unbounded();
870
871 let jh = tokio::task::spawn(async move {
872 tokio::time::sleep(Duration::from_millis(2)).await;
873 pin_mut!(tx);
874 tx.send_all(&mut futures::stream::iter([3, 1, 2, 4]).map(Ok)).await?;
875
876 tokio::time::sleep(Duration::from_millis(150)).await;
877
878 tx.send_all(&mut futures::stream::iter([6, 5, 7]).map(Ok)).await?;
879
880 anyhow::Ok(())
881 });
882
883 let chunks = rx
884 .sequencer(Duration::from_millis(50), 1024)
885 .try_ready_chunks(10)
886 .try_collect::<Vec<Vec<_>>>()
887 .await?;
888
889 assert_eq!(chunks, vec![vec![1, 2, 3, 4], vec![5, 6, 7]]);
890 jh.await??;
891
892 Ok(())
893 }
894}