1use std::collections::VecDeque;
30
31use crate::flight::RunId;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Admission {
36 Cleared,
38 Queued {
40 ahead: usize,
42 },
43}
44
45impl Admission {
46 #[must_use]
48 pub fn is_cleared(self) -> bool {
49 matches!(self, Self::Cleared)
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct Slots {
56 capacity: usize,
57 live: Vec<RunId>,
58 waiting: VecDeque<RunId>,
59}
60
61impl Slots {
62 #[must_use]
67 pub fn new(capacity: usize) -> Self {
68 Self {
69 capacity: capacity.max(1),
70 live: Vec::new(),
71 waiting: VecDeque::new(),
72 }
73 }
74
75 #[must_use]
77 pub fn capacity(&self) -> usize {
78 self.capacity
79 }
80
81 #[must_use]
83 pub fn live(&self) -> usize {
84 self.live.len()
85 }
86
87 #[must_use]
89 pub fn waiting(&self) -> usize {
90 self.waiting.len()
91 }
92
93 #[must_use]
95 pub fn is_full(&self) -> bool {
96 self.live.len() >= self.capacity
97 }
98
99 #[must_use]
101 pub fn is_live(&self, run: &RunId) -> bool {
102 self.live.contains(run)
103 }
104
105 pub fn request(&mut self, run: RunId) -> Admission {
111 if self.live.contains(&run) {
112 return Admission::Cleared;
113 }
114 if let Some(position) = self.waiting.iter().position(|queued| *queued == run) {
115 return Admission::Queued { ahead: position };
116 }
117
118 if self.is_full() {
119 self.waiting.push_back(run);
120 return Admission::Queued {
121 ahead: self.waiting.len() - 1,
122 };
123 }
124
125 self.live.push(run);
126 Admission::Cleared
127 }
128
129 pub fn release(&mut self, run: &RunId) -> Option<RunId> {
134 if let Some(index) = self.waiting.iter().position(|queued| queued == run) {
135 self.waiting.remove(index);
136 return None;
137 }
138
139 let index = self.live.iter().position(|held| held == run)?;
140 self.live.remove(index);
141
142 let next = self.waiting.pop_front()?;
143 self.live.push(next.clone());
144 Some(next)
145 }
146
147 pub fn queue(&self) -> impl Iterator<Item = &RunId> {
149 self.waiting.iter()
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn run() -> RunId {
158 RunId::generate()
159 }
160
161 #[test]
162 fn runs_start_immediately_while_there_is_room() {
163 let mut slots = Slots::new(3);
164
165 for _ in 0..3 {
166 assert_eq!(slots.request(run()), Admission::Cleared);
167 }
168 assert_eq!(slots.live(), 3);
169 assert!(slots.is_full());
170 }
171
172 #[test]
173 fn the_rest_wait_rather_than_being_turned_away() {
174 let mut slots = Slots::new(2);
178 slots.request(run());
179 slots.request(run());
180
181 assert_eq!(slots.request(run()), Admission::Queued { ahead: 0 });
182 assert_eq!(slots.request(run()), Admission::Queued { ahead: 1 });
183 assert_eq!(slots.waiting(), 2);
184 }
185
186 #[test]
187 fn finishing_a_run_lets_the_next_one_in() {
188 let mut slots = Slots::new(1);
189 let first = run();
190 let second = run();
191 slots.request(first.clone());
192 slots.request(second.clone());
193
194 let started = slots.release(&first);
195
196 assert_eq!(started, Some(second.clone()));
197 assert!(slots.is_live(&second));
198 assert_eq!(slots.waiting(), 0);
199 }
200
201 #[test]
202 fn the_queue_is_served_in_order() {
203 let mut slots = Slots::new(1);
206 let held = run();
207 slots.request(held.clone());
208
209 let queued: Vec<RunId> = (0..3).map(|_| run()).collect();
210 for run in &queued {
211 slots.request(run.clone());
212 }
213
214 assert_eq!(slots.queue().cloned().collect::<Vec<_>>(), queued);
215 assert_eq!(slots.release(&held), Some(queued[0].clone()));
216 }
217
218 #[test]
219 fn asking_twice_does_not_take_two_slots() {
220 let mut slots = Slots::new(2);
223 let run = run();
224
225 assert_eq!(slots.request(run.clone()), Admission::Cleared);
226 assert_eq!(slots.request(run.clone()), Admission::Cleared);
227 assert_eq!(slots.live(), 1);
228 }
229
230 #[test]
231 fn asking_twice_while_queued_reports_the_same_place_in_line() {
232 let mut slots = Slots::new(1);
233 slots.request(run());
234 let waiting = run();
235
236 assert_eq!(
237 slots.request(waiting.clone()),
238 Admission::Queued { ahead: 0 }
239 );
240 assert_eq!(slots.request(waiting), Admission::Queued { ahead: 0 });
241 assert_eq!(slots.waiting(), 1);
242 }
243
244 #[test]
245 fn a_run_cancelled_while_waiting_leaves_the_queue_without_taking_a_slot() {
246 let mut slots = Slots::new(1);
247 let held = run();
248 let abandoned = run();
249 let next = run();
250 slots.request(held.clone());
251 slots.request(abandoned.clone());
252 slots.request(next.clone());
253
254 assert_eq!(slots.release(&abandoned), None, "it never held a slot");
255 assert_eq!(slots.waiting(), 1);
256 assert_eq!(slots.release(&held), Some(next));
257 }
258
259 #[test]
260 fn releasing_something_unknown_changes_nothing() {
261 let mut slots = Slots::new(2);
262 slots.request(run());
263
264 assert_eq!(slots.release(&run()), None);
265 assert_eq!(slots.live(), 1);
266 }
267
268 #[test]
269 fn a_capacity_of_zero_still_runs_one_at_a_time() {
270 let mut slots = Slots::new(0);
272
273 assert_eq!(slots.capacity(), 1);
274 assert!(slots.request(run()).is_cleared());
275 }
276
277 #[test]
278 fn fifty_arrivals_at_a_capacity_of_four_leave_four_running_and_none_lost() {
279 let mut slots = Slots::new(4);
282 let arrivals: Vec<RunId> = (0..50).map(|_| run()).collect();
283
284 for run in &arrivals {
285 slots.request(run.clone());
286 }
287
288 assert_eq!(slots.live(), 4);
289 assert_eq!(slots.waiting(), 46, "the rest are delayed, not dropped");
290
291 let mut admitted = 4;
294 while slots.waiting() > 0 {
295 let holder = arrivals
296 .iter()
297 .find(|run| slots.is_live(run))
298 .cloned()
299 .expect("something is live while anything waits");
300
301 assert!(slots.release(&holder).is_some(), "a release must admit one");
302 admitted += 1;
303 assert!(admitted <= arrivals.len(), "the queue stopped draining");
304 }
305
306 assert_eq!(admitted, arrivals.len(), "every arrival eventually started");
307 }
308}