use crate::snapshot::TimerControlFailure;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum TimerRegistration {
#[default]
Unregistered,
Scheduled {
generation: u64,
deadline_ns: u64,
},
Running {
generation: u64,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WakeupArm {
Initial,
Replacement,
}
impl WakeupArm {
pub(crate) const fn replaces_existing(self) -> bool {
matches!(self, Self::Replacement)
}
}
#[derive(Debug, Default)]
pub struct TimerControl {
generation: u64,
registration: TimerRegistration,
}
#[derive(Clone, Copy)]
enum DeadlineSelection {
Earliest,
Exact,
}
impl DeadlineSelection {
const fn replaces(self, current_deadline_ns: u64, requested_deadline_ns: u64) -> bool {
match self {
Self::Earliest => requested_deadline_ns < current_deadline_ns,
Self::Exact => requested_deadline_ns != current_deadline_ns,
}
}
}
impl TimerControl {
#[must_use]
pub(crate) const fn generation(&self) -> u64 {
self.generation
}
#[cfg(test)]
pub(crate) const fn exhaust_generation_for_test(&mut self) {
self.generation = u64::MAX;
}
#[must_use]
pub(crate) const fn registration(&self) -> TimerRegistration {
self.registration
}
pub(crate) const fn terminate(&mut self) -> bool {
let clear_wakeup = matches!(self.registration, TimerRegistration::Scheduled { .. });
self.registration = TimerRegistration::Unregistered;
clear_wakeup
}
pub(crate) fn schedule(
&mut self,
deadline_ns: u64,
) -> Result<Option<WakeupArm>, TimerControlFailure> {
self.request_deadline(deadline_ns, DeadlineSelection::Earliest)
}
pub(crate) fn cancel(&mut self) -> Result<(), TimerControlFailure> {
if matches!(self.registration, TimerRegistration::Scheduled { .. }) {
let generation = self.next_generation()?;
self.generation = generation;
self.registration = TimerRegistration::Unregistered;
}
Ok(())
}
pub(crate) fn reconcile(
&mut self,
deadline_ns: u64,
) -> Result<Option<WakeupArm>, TimerControlFailure> {
self.request_deadline(deadline_ns, DeadlineSelection::Exact)
}
fn request_deadline(
&mut self,
deadline_ns: u64,
selection: DeadlineSelection,
) -> Result<Option<WakeupArm>, TimerControlFailure> {
let kind = match self.registration {
TimerRegistration::Unregistered => WakeupArm::Initial,
TimerRegistration::Scheduled {
deadline_ns: current_deadline_ns,
..
} if selection.replaces(current_deadline_ns, deadline_ns) => WakeupArm::Replacement,
TimerRegistration::Scheduled { .. } | TimerRegistration::Running { .. } => {
return Ok(None);
}
};
self.arm_deadline(deadline_ns)?;
Ok(Some(kind))
}
pub(crate) const fn begin(&mut self, generation: u64) -> bool {
match self.registration {
TimerRegistration::Scheduled {
generation: scheduled_generation,
..
} if scheduled_generation == generation => {
self.registration = TimerRegistration::Running { generation };
true
}
TimerRegistration::Unregistered
| TimerRegistration::Scheduled { .. }
| TimerRegistration::Running { .. } => false,
}
}
pub(crate) fn complete_running(
&mut self,
next_deadline_ns: Option<u64>,
) -> Result<(), TimerControlFailure> {
if let Some(deadline_ns) = next_deadline_ns {
self.arm_deadline(deadline_ns)?;
} else {
self.registration = TimerRegistration::Unregistered;
}
Ok(())
}
fn arm_deadline(&mut self, deadline_ns: u64) -> Result<(), TimerControlFailure> {
let generation = self.next_generation()?;
self.generation = generation;
self.registration = TimerRegistration::Scheduled {
generation,
deadline_ns,
};
Ok(())
}
fn next_generation(&self) -> Result<u64, TimerControlFailure> {
self.generation
.checked_add(1)
.ok_or(TimerControlFailure::GenerationExhausted)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn arm(control: &mut TimerControl, deadline_ns: u64) -> u64 {
assert_eq!(control.schedule(deadline_ns), Ok(Some(WakeupArm::Initial)));
let TimerRegistration::Scheduled { generation, .. } = control.registration() else {
panic!("initial schedule should arm");
};
generation
}
#[test]
fn duplicate_and_later_schedules_keep_one_earliest_handle() {
let mut control = TimerControl::default();
assert_eq!(arm(&mut control, 100), 1);
assert_eq!(control.schedule(100), Ok(None));
assert_eq!(control.schedule(200), Ok(None));
assert_eq!(
control.registration(),
TimerRegistration::Scheduled {
generation: 1,
deadline_ns: 100
}
);
}
#[test]
fn earlier_schedule_replaces_and_invalidates_old_generation() {
let mut control = TimerControl::default();
let old_generation = arm(&mut control, 100);
assert_eq!(control.schedule(50), Ok(Some(WakeupArm::Replacement)));
assert_eq!(
control.registration(),
TimerRegistration::Scheduled {
generation: 2,
deadline_ns: 50,
}
);
assert!(!control.begin(old_generation));
assert!(control.begin(2));
}
#[test]
fn authoritative_reconciliation_can_move_scheduled_deadline_later() {
let mut control = TimerControl::default();
let old_generation = arm(&mut control, 100);
assert_eq!(control.reconcile(200), Ok(Some(WakeupArm::Replacement)));
assert_eq!(
control.registration(),
TimerRegistration::Scheduled {
generation: 2,
deadline_ns: 200,
}
);
assert!(!control.begin(old_generation));
assert!(control.begin(2));
}
#[test]
fn running_requests_leave_completion_selection_to_the_registry() {
let mut control = TimerControl::default();
let generation = arm(&mut control, 100);
assert!(control.begin(generation));
assert_eq!(control.reconcile(300), Ok(None));
assert_eq!(control.schedule(90), Ok(None));
assert_eq!(control.cancel(), Ok(()));
assert_eq!(
control.registration(),
TimerRegistration::Running { generation }
);
assert_eq!(control.generation(), generation);
}
#[test]
fn completion_arms_the_supplied_deadline() {
for deadline_ns in [90, 300] {
let mut control = TimerControl::default();
let generation = arm(&mut control, 100);
assert!(control.begin(generation));
assert_eq!(control.complete_running(Some(deadline_ns)), Ok(()));
assert_eq!(
control.registration(),
TimerRegistration::Scheduled {
generation: 2,
deadline_ns,
}
);
assert_eq!(control.generation(), 2);
}
}
#[test]
fn completion_without_a_successor_stops_running_work() {
let mut control = TimerControl::default();
let generation = arm(&mut control, 100);
assert!(control.begin(generation));
assert_eq!(control.complete_running(None), Ok(()));
assert_eq!(control.registration(), TimerRegistration::Unregistered);
}
#[test]
fn scheduled_cancel_invalidates_consumed_generation() {
let mut control = TimerControl::default();
let generation = arm(&mut control, 100);
assert_eq!(control.cancel(), Ok(()));
assert!(!control.begin(generation));
assert_eq!(control.generation(), 2);
assert_eq!(control.registration(), TimerRegistration::Unregistered);
assert_eq!(control.cancel(), Ok(()));
assert_eq!(control.generation(), 2);
assert_eq!(control.registration(), TimerRegistration::Unregistered);
}
#[test]
fn exhausted_generation_rejects_successors_without_preventing_stop() {
let mut control = TimerControl {
generation: u64::MAX,
registration: TimerRegistration::Scheduled {
generation: u64::MAX,
deadline_ns: 100,
},
};
assert_eq!(
control.schedule(50),
Err(TimerControlFailure::GenerationExhausted)
);
assert_eq!(
control.cancel(),
Err(TimerControlFailure::GenerationExhausted)
);
assert_eq!(
control.registration(),
TimerRegistration::Scheduled {
generation: u64::MAX,
deadline_ns: 100
}
);
assert!(control.begin(u64::MAX));
assert_eq!(
control.complete_running(Some(200)),
Err(TimerControlFailure::GenerationExhausted)
);
assert_eq!(
control.registration(),
TimerRegistration::Running {
generation: u64::MAX
}
);
assert_eq!(
control.complete_running(None),
Ok(()),
"stopping does not allocate a successor generation"
);
assert_eq!(control.generation(), u64::MAX);
assert_eq!(control.registration(), TimerRegistration::Unregistered);
}
}