1//! Utility for tracking network requests that will be retried in the future.
23use core::cmp::Ordering;
4use std::collections::BinaryHeap;
5use std::time::{Duration, Instant};
67/// A tracker for network requests that have failed, and are awaiting to be
8/// retried in the future.
9pub struct SleepTracker<T> {
10/// This is a priority queue that tracks the time when the next sleeper
11 /// should awaken (based on the [`Sleeper::wakeup`] property).
12heap: BinaryHeap<Sleeper<T>>,
13}
1415/// An individual network request that is waiting to be retried in the future.
16struct Sleeper<T> {
17/// The time when this requests should be retried.
18wakeup: Instant,
19/// Information about the network request.
20data: T,
21}
2223impl<T> PartialEq for Sleeper<T> {
24fn eq(&self, other: &Sleeper<T>) -> bool {
25self.wakeup == other.wakeup
26 }
27}
2829impl<T> PartialOrd for Sleeper<T> {
30fn partial_cmp(&self, other: &Sleeper<T>) -> Option<Ordering> {
31// This reverses the comparison so that the BinaryHeap tracks the
32 // entry with the *lowest* wakeup time.
33Some(other.wakeup.cmp(&self.wakeup))
34 }
35}
3637impl<T> Eq for Sleeper<T> {}
3839impl<T> Ord for Sleeper<T> {
40fn cmp(&self, other: &Sleeper<T>) -> Ordering {
41self.wakeup.cmp(&other.wakeup)
42 }
43}
4445impl<T> SleepTracker<T> {
46pub fn new() -> SleepTracker<T> {
47 SleepTracker {
48 heap: BinaryHeap::new(),
49 }
50 }
5152/// Adds a new download that should be retried in the future.
53pub fn push(&mut self, sleep: u64, data: T) {
54self.heap.push(Sleeper {
55 wakeup: Instant::now()
56 .checked_add(Duration::from_millis(sleep))
57 .expect("instant should not wrap"),
58 data,
59 });
60 }
6162pub fn len(&self) -> usize {
63self.heap.len()
64 }
6566/// Returns any downloads that are ready to go now.
67pub fn to_retry(&mut self) -> Vec<T> {
68let now = Instant::now();
69let mut result = Vec::new();
70while let Some(next) = self.heap.peek() {
71if next.wakeup < now {
72 result.push(self.heap.pop().unwrap().data);
73 } else {
74break;
75 }
76 }
77 result
78 }
7980/// Returns the time when the next download is ready to go.
81 ///
82 /// Returns None if there are no sleepers remaining.
83pub fn time_to_next(&self) -> Option<Duration> {
84self.heap
85 .peek()
86 .map(|s| s.wakeup.saturating_duration_since(Instant::now()))
87 }
88}
8990#[test]
91fn returns_in_order() {
92let mut s = SleepTracker::new();
93 s.push(30_000, 30_000);
94 s.push(1, 1);
95assert_eq!(s.len(), 2);
96 std::thread::sleep(Duration::from_millis(2));
97assert_eq!(s.to_retry(), &[1]);
98assert!(s.to_retry().is_empty());
99let next = s.time_to_next().expect("should be next");
100assert!(
101 next < Duration::from_millis(30_000),
102"{next:?} should be less than 30s"
103);
104}