use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum EntryState {
Scheduled = 0,
Cancelled = 1,
Dispatching = 2,
Expired = 3,
Rejected = 4,
}
#[derive(Debug)]
pub(crate) struct TimeoutState {
state: AtomicU8,
}
impl TimeoutState {
pub(crate) fn scheduled() -> Self {
Self {
state: AtomicU8::new(EntryState::Scheduled as u8),
}
}
pub(crate) fn load(&self) -> EntryState {
match self.state.load(Ordering::Acquire) {
0 => EntryState::Scheduled,
1 => EntryState::Cancelled,
2 => EntryState::Dispatching,
3 => EntryState::Expired,
_ => EntryState::Rejected,
}
}
pub(crate) fn cancel(&self) -> bool {
self.state
.compare_exchange(
EntryState::Scheduled as u8,
EntryState::Cancelled as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
pub(crate) fn dispatch(&self) -> bool {
self.state
.compare_exchange(
EntryState::Scheduled as u8,
EntryState::Dispatching as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
pub(crate) fn reschedule(&self) -> bool {
self.state
.compare_exchange(
EntryState::Dispatching as u8,
EntryState::Scheduled as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
pub(crate) fn expire(&self) -> bool {
self.state
.compare_exchange(
EntryState::Dispatching as u8,
EntryState::Expired as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
pub(crate) fn reject(&self) -> bool {
self.state
.compare_exchange(
EntryState::Dispatching as u8,
EntryState::Rejected as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
}
#[cfg(test)]
mod tests {
use super::{EntryState, TimeoutState};
#[test]
fn state_moves_from_scheduled_to_cancelled_once() {
let state = TimeoutState::scheduled();
assert!(state.cancel());
assert!(!state.cancel());
assert_eq!(state.load(), EntryState::Cancelled);
}
#[test]
fn dispatch_can_expire_or_reschedule() {
let state = TimeoutState::scheduled();
assert!(state.dispatch());
assert!(state.reschedule());
assert!(state.dispatch());
assert!(state.expire());
assert_eq!(state.load(), EntryState::Expired);
}
#[test]
fn dispatch_can_reject_once() {
let state = TimeoutState::scheduled();
assert!(state.dispatch());
assert!(state.reject());
assert!(!state.reject());
assert_eq!(state.load(), EntryState::Rejected);
}
}