use crate::{
schedule::ResolvedSchedule,
snapshot::{InactiveReason, TimerControlFailure},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OrdinaryPending {
Cancel,
Reconcile(ResolvedSchedule),
Unregister,
Schedule(ResolvedSchedule),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TimerRegistration {
Inactive {
reason: InactiveReason,
},
Scheduled {
generation: u64,
deadline_ns: u64,
},
Running {
generation: u64,
pending: Option<OrdinaryPending>,
},
}
impl Default for TimerRegistration {
fn default() -> Self {
Self::Inactive {
reason: InactiveReason::NeverScheduled,
}
}
}
#[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,
pub(crate) 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 pending(&self) -> Option<OrdinaryPending> {
match self.registration {
TimerRegistration::Running { pending, .. } => pending,
TimerRegistration::Inactive { .. } | TimerRegistration::Scheduled { .. } => None,
}
}
pub(crate) const fn terminate(&mut self, reason: InactiveReason) -> bool {
let clear_wakeup = matches!(self.registration, TimerRegistration::Scheduled { .. });
self.registration = TimerRegistration::Inactive { reason };
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::Inactive {
reason: InactiveReason::Cancelled,
};
}
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::Inactive { .. } => 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,
pending: None,
};
true
}
TimerRegistration::Inactive { .. }
| TimerRegistration::Scheduled { .. }
| TimerRegistration::Running { .. } => false,
}
}
pub(crate) 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));
let TimerRegistration::Running { pending, .. } = &mut control.registration else {
panic!("accepted fixture should be running");
};
*pending = Some(OrdinaryPending::Unregister);
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,
pending: Some(OrdinaryPending::Unregister),
}
);
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.arm_deadline(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!(!control.terminate(InactiveReason::Stopped));
assert_eq!(
control.registration,
TimerRegistration::Inactive {
reason: InactiveReason::Stopped,
}
);
}
#[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::Inactive {
reason: InactiveReason::Cancelled,
}
);
assert_eq!(control.cancel(), Ok(()));
assert_eq!(control.generation(), 2);
assert_eq!(
control.registration,
TimerRegistration::Inactive {
reason: InactiveReason::Cancelled,
}
);
}
#[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));
let TimerRegistration::Running { pending, .. } = &mut control.registration else {
panic!("accepted fixture should be running");
};
*pending = Some(OrdinaryPending::Cancel);
assert_eq!(
control.arm_deadline(200),
Err(TimerControlFailure::GenerationExhausted)
);
assert_eq!(
control.registration,
TimerRegistration::Running {
generation: u64::MAX,
pending: Some(OrdinaryPending::Cancel),
}
);
assert!(!control.terminate(InactiveReason::Cancelled));
assert_eq!(control.generation(), u64::MAX);
assert_eq!(
control.registration,
TimerRegistration::Inactive {
reason: InactiveReason::Cancelled,
}
);
}
}