use std::collections::VecDeque;
use crate::flight::RunId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Admission {
Cleared,
Queued {
ahead: usize,
},
}
impl Admission {
#[must_use]
pub fn is_cleared(self) -> bool {
matches!(self, Self::Cleared)
}
}
#[derive(Debug, Clone)]
pub struct Slots {
capacity: usize,
live: Vec<RunId>,
waiting: VecDeque<RunId>,
}
impl Slots {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
capacity: capacity.max(1),
live: Vec::new(),
waiting: VecDeque::new(),
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
#[must_use]
pub fn live(&self) -> usize {
self.live.len()
}
#[must_use]
pub fn waiting(&self) -> usize {
self.waiting.len()
}
#[must_use]
pub fn is_full(&self) -> bool {
self.live.len() >= self.capacity
}
#[must_use]
pub fn is_live(&self, run: &RunId) -> bool {
self.live.contains(run)
}
pub fn request(&mut self, run: RunId) -> Admission {
if self.live.contains(&run) {
return Admission::Cleared;
}
if let Some(position) = self.waiting.iter().position(|queued| *queued == run) {
return Admission::Queued { ahead: position };
}
if self.is_full() {
self.waiting.push_back(run);
return Admission::Queued {
ahead: self.waiting.len() - 1,
};
}
self.live.push(run);
Admission::Cleared
}
pub fn release(&mut self, run: &RunId) -> Option<RunId> {
if let Some(index) = self.waiting.iter().position(|queued| queued == run) {
self.waiting.remove(index);
return None;
}
let index = self.live.iter().position(|held| held == run)?;
self.live.remove(index);
let next = self.waiting.pop_front()?;
self.live.push(next.clone());
Some(next)
}
pub fn queue(&self) -> impl Iterator<Item = &RunId> {
self.waiting.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn run() -> RunId {
RunId::generate()
}
#[test]
fn runs_start_immediately_while_there_is_room() {
let mut slots = Slots::new(3);
for _ in 0..3 {
assert_eq!(slots.request(run()), Admission::Cleared);
}
assert_eq!(slots.live(), 3);
assert!(slots.is_full());
}
#[test]
fn the_rest_wait_rather_than_being_turned_away() {
let mut slots = Slots::new(2);
slots.request(run());
slots.request(run());
assert_eq!(slots.request(run()), Admission::Queued { ahead: 0 });
assert_eq!(slots.request(run()), Admission::Queued { ahead: 1 });
assert_eq!(slots.waiting(), 2);
}
#[test]
fn finishing_a_run_lets_the_next_one_in() {
let mut slots = Slots::new(1);
let first = run();
let second = run();
slots.request(first.clone());
slots.request(second.clone());
let started = slots.release(&first);
assert_eq!(started, Some(second.clone()));
assert!(slots.is_live(&second));
assert_eq!(slots.waiting(), 0);
}
#[test]
fn the_queue_is_served_in_order() {
let mut slots = Slots::new(1);
let held = run();
slots.request(held.clone());
let queued: Vec<RunId> = (0..3).map(|_| run()).collect();
for run in &queued {
slots.request(run.clone());
}
assert_eq!(slots.queue().cloned().collect::<Vec<_>>(), queued);
assert_eq!(slots.release(&held), Some(queued[0].clone()));
}
#[test]
fn asking_twice_does_not_take_two_slots() {
let mut slots = Slots::new(2);
let run = run();
assert_eq!(slots.request(run.clone()), Admission::Cleared);
assert_eq!(slots.request(run.clone()), Admission::Cleared);
assert_eq!(slots.live(), 1);
}
#[test]
fn asking_twice_while_queued_reports_the_same_place_in_line() {
let mut slots = Slots::new(1);
slots.request(run());
let waiting = run();
assert_eq!(
slots.request(waiting.clone()),
Admission::Queued { ahead: 0 }
);
assert_eq!(slots.request(waiting), Admission::Queued { ahead: 0 });
assert_eq!(slots.waiting(), 1);
}
#[test]
fn a_run_cancelled_while_waiting_leaves_the_queue_without_taking_a_slot() {
let mut slots = Slots::new(1);
let held = run();
let abandoned = run();
let next = run();
slots.request(held.clone());
slots.request(abandoned.clone());
slots.request(next.clone());
assert_eq!(slots.release(&abandoned), None, "it never held a slot");
assert_eq!(slots.waiting(), 1);
assert_eq!(slots.release(&held), Some(next));
}
#[test]
fn releasing_something_unknown_changes_nothing() {
let mut slots = Slots::new(2);
slots.request(run());
assert_eq!(slots.release(&run()), None);
assert_eq!(slots.live(), 1);
}
#[test]
fn a_capacity_of_zero_still_runs_one_at_a_time() {
let mut slots = Slots::new(0);
assert_eq!(slots.capacity(), 1);
assert!(slots.request(run()).is_cleared());
}
#[test]
fn fifty_arrivals_at_a_capacity_of_four_leave_four_running_and_none_lost() {
let mut slots = Slots::new(4);
let arrivals: Vec<RunId> = (0..50).map(|_| run()).collect();
for run in &arrivals {
slots.request(run.clone());
}
assert_eq!(slots.live(), 4);
assert_eq!(slots.waiting(), 46, "the rest are delayed, not dropped");
let mut admitted = 4;
while slots.waiting() > 0 {
let holder = arrivals
.iter()
.find(|run| slots.is_live(run))
.cloned()
.expect("something is live while anything waits");
assert!(slots.release(&holder).is_some(), "a release must admit one");
admitted += 1;
assert!(admitted <= arrivals.len(), "the queue stopped draining");
}
assert_eq!(admitted, arrivals.len(), "every arrival eventually started");
}
}