spire_core/frontier/
memory.rs1use std::cmp::Ordering;
4use std::collections::{BinaryHeap, VecDeque};
5use std::fmt;
6use std::future::{Future, ready};
7use std::sync::Mutex;
8
9use crate::Result;
10use crate::frontier::seed::Seed;
11use crate::frontier::{Ack, Admission, Frontier, Next, Report};
12use crate::sync::lock;
13
14pub struct InMemFrontier {
17 seeds: Mutex<Store>,
18}
19
20enum Store {
21 Fifo(VecDeque<Seed>),
22 Lifo(VecDeque<Seed>),
23 Priority(BinaryHeap<Prioritized>, u64),
24}
25
26struct Prioritized {
28 priority: i32,
29 sequence: u64,
30 seed: Seed,
31}
32
33impl Ord for Prioritized {
34 fn cmp(&self, other: &Self) -> Ordering {
35 self.priority
36 .cmp(&other.priority)
37 .then_with(|| other.sequence.cmp(&self.sequence))
38 }
39}
40
41impl PartialOrd for Prioritized {
42 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
43 Some(self.cmp(other))
44 }
45}
46
47impl PartialEq for Prioritized {
48 fn eq(&self, other: &Self) -> bool {
49 self.cmp(other).is_eq()
50 }
51}
52
53impl Eq for Prioritized {}
54
55impl Store {
56 fn len(&self) -> usize {
57 match self {
58 Self::Fifo(seeds) | Self::Lifo(seeds) => seeds.len(),
59 Self::Priority(seeds, _) => seeds.len(),
60 }
61 }
62
63 const fn order(&self) -> &'static str {
64 match self {
65 Self::Fifo(_) => "fifo",
66 Self::Lifo(_) => "lifo",
67 Self::Priority(..) => "priority",
68 }
69 }
70}
71
72impl InMemFrontier {
73 #[must_use]
75 pub const fn fifo() -> Self {
76 Self::with_store(Store::Fifo(VecDeque::new()))
77 }
78
79 #[must_use]
81 pub const fn lifo() -> Self {
82 Self::with_store(Store::Lifo(VecDeque::new()))
83 }
84
85 #[must_use]
88 pub const fn priority() -> Self {
89 Self::with_store(Store::Priority(BinaryHeap::new(), 0))
90 }
91
92 const fn with_store(store: Store) -> Self {
93 Self {
94 seeds: Mutex::new(store),
95 }
96 }
97
98 fn insert(&self, seed: Seed) {
99 match &mut *lock(&self.seeds) {
100 Store::Fifo(seeds) | Store::Lifo(seeds) => seeds.push_back(seed),
101 Store::Priority(seeds, sequence) => {
102 *sequence += 1;
103 let priority = seed.priority();
104 seeds.push(Prioritized {
105 priority,
106 sequence: *sequence,
107 seed,
108 });
109 }
110 }
111 }
112}
113
114impl Default for InMemFrontier {
115 fn default() -> Self {
116 Self::fifo()
117 }
118}
119
120impl fmt::Debug for InMemFrontier {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 let store = lock(&self.seeds);
123 f.debug_struct("InMemFrontier")
124 .field("order", &store.order())
125 .field("len", &store.len())
126 .finish()
127 }
128}
129
130impl Frontier for InMemFrontier {
131 fn push(&self, seed: Seed) -> impl Future<Output = Result<Admission>> + Send {
132 self.insert(seed);
133 ready(Ok(Admission::Queued))
134 }
135
136 fn pop(&self) -> impl Future<Output = Result<Next>> + Send {
137 let seed = match &mut *lock(&self.seeds) {
138 Store::Fifo(seeds) => seeds.pop_front(),
139 Store::Lifo(seeds) => seeds.pop_back(),
140 Store::Priority(seeds, _) => seeds.pop().map(|prioritized| prioritized.seed),
141 };
142 ready(Ok(seed.map_or(Next::Empty, Next::Ready)))
143 }
144
145 fn report(&self, report: Report) -> impl Future<Output = Result<Ack>> + Send {
148 let ack = match report {
149 Report::Cancelled(seed) => {
150 self.insert(seed);
151 Ack::Applied
152 }
153 Report::Retry { .. } => Ack::RetryRefused,
154 _ => Ack::Applied,
155 };
156 ready(Ok(ack))
157 }
158
159 fn size_hint(&self) -> Option<usize> {
160 Some(lock(&self.seeds).len())
161 }
162
163 fn snapshot(&self) -> Option<Vec<Seed>> {
164 let store = lock(&self.seeds);
165 let seeds = match &*store {
166 Store::Fifo(seeds) | Store::Lifo(seeds) => seeds.iter().cloned().collect(),
167 Store::Priority(seeds, _) => seeds.iter().map(|entry| entry.seed.clone()).collect(),
168 };
169 Some(seeds)
170 }
171}
172
173impl Extend<Seed> for InMemFrontier {
174 fn extend<I: IntoIterator<Item = Seed>>(&mut self, seeds: I) {
177 for seed in seeds {
178 self.insert(seed);
179 }
180 }
181}
182
183impl FromIterator<Seed> for InMemFrontier {
184 fn from_iter<I: IntoIterator<Item = Seed>>(seeds: I) -> Self {
186 let mut frontier = Self::fifo();
187 frontier.extend(seeds);
188 frontier
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::frontier::test_util::{pop_path, seed};
196
197 #[tokio::test]
198 async fn fifo_and_lifo_order() {
199 let fifo = InMemFrontier::fifo();
200 let lifo = InMemFrontier::lifo();
201 for path in ["a", "b"] {
202 assert!(fifo.push(seed(path)).await.unwrap().is_queued());
203 assert!(lifo.push(seed(path)).await.unwrap().is_queued());
204 }
205 assert_eq!(fifo.size_hint(), Some(2));
206
207 assert_eq!(pop_path(&fifo).await.as_deref(), Some("a"));
208 assert_eq!(pop_path(&lifo).await.as_deref(), Some("b"));
209 assert_eq!(pop_path(&fifo).await.as_deref(), Some("b"));
210 assert!(matches!(fifo.pop().await.unwrap(), Next::Empty));
211 }
212
213 #[tokio::test]
214 async fn priority_order() {
215 let frontier = InMemFrontier::priority();
216 for (path, priority) in [("low", -1), ("first", 0), ("high", 5), ("second", 0)] {
217 frontier
218 .push(seed(path).with_priority(priority))
219 .await
220 .unwrap();
221 }
222 let mut order = Vec::new();
223 while let Some(path) = pop_path(&frontier).await {
224 order.push(path);
225 }
226 assert_eq!(order, ["high", "first", "second", "low"]);
227 }
228}