use std::collections::VecDeque;
use tokio::time::Instant;
use crate::TaskSpec;
use crate::identity::TaskId;
pub(super) struct SlotState {
pub status: SlotStatus,
pub running_id: Option<TaskId>,
pub queue: VecDeque<(TaskId, TaskSpec)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum SlotStatus {
Idle,
Admitting {
since: Instant,
},
Running {
started_at: Instant,
},
Terminating {
cancelled_at: Instant,
},
}
impl SlotStatus {
pub fn label(&self) -> &'static str {
match self {
SlotStatus::Idle => "idle",
SlotStatus::Admitting { .. } => "admitting",
SlotStatus::Running { .. } => "running",
SlotStatus::Terminating { .. } => "terminating",
}
}
}
impl SlotState {
pub fn new() -> Self {
Self {
status: SlotStatus::Idle,
running_id: None,
queue: VecDeque::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_slot_is_idle_with_empty_queue() {
let slot = SlotState::new();
assert_eq!(slot.status, SlotStatus::Idle);
assert!(slot.running_id.is_none());
assert!(slot.queue.is_empty());
}
#[test]
fn only_idle_is_treated_as_a_free_slot() {
let now = Instant::now();
assert!(matches!(SlotStatus::Idle, SlotStatus::Idle));
for occupied in [
SlotStatus::Admitting { since: now },
SlotStatus::Running { started_at: now },
SlotStatus::Terminating { cancelled_at: now },
] {
assert!(
!matches!(occupied, SlotStatus::Idle),
"{} must count as occupied, not free",
occupied.label()
);
}
}
#[test]
fn labels_are_stable_and_do_not_include_timestamps() {
let now = Instant::now();
assert_eq!(SlotStatus::Idle.label(), "idle");
assert_eq!(SlotStatus::Admitting { since: now }.label(), "admitting");
assert_eq!(SlotStatus::Running { started_at: now }.label(), "running");
assert_eq!(
SlotStatus::Terminating { cancelled_at: now }.label(),
"terminating"
);
}
#[test]
fn queue_push_pop_fifo() {
let mut slot = SlotState::new();
slot.queue.push_back((TaskId::next(), make_spec("a")));
slot.queue.push_back((TaskId::next(), make_spec("b")));
slot.queue.push_back((TaskId::next(), make_spec("c")));
assert_eq!(slot.queue.len(), 3);
assert_eq!(slot.queue.pop_front().unwrap().1.name(), "a");
assert_eq!(slot.queue.pop_front().unwrap().1.name(), "b");
assert_eq!(slot.queue.pop_front().unwrap().1.name(), "c");
assert!(slot.queue.is_empty());
}
fn make_spec(name: &str) -> TaskSpec {
use crate::TaskContext;
use crate::{BackoffPolicy, RestartPolicy, TaskFn, TaskRef};
let task: TaskRef = TaskFn::arc(name, |_ctx: TaskContext| async { Ok(()) });
TaskSpec::new(task, RestartPolicy::Never, BackoffPolicy::default(), None)
}
}