use core::sync::atomic::{AtomicU8, Ordering};
use crate::error::{PoaError, PoaResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum PoaManagerState {
Holding = 0,
Active = 1,
Discarding = 2,
Inactive = 3,
}
impl PoaManagerState {
fn from_u8(v: u8) -> PoaManagerState {
match v {
0 => Self::Holding,
1 => Self::Active,
2 => Self::Discarding,
_ => Self::Inactive,
}
}
}
#[derive(Debug)]
pub struct PoaManager {
state: AtomicU8,
}
impl PoaManager {
#[must_use]
pub const fn new() -> Self {
Self {
state: AtomicU8::new(PoaManagerState::Holding as u8),
}
}
#[must_use]
pub fn state(&self) -> PoaManagerState {
PoaManagerState::from_u8(self.state.load(Ordering::Acquire))
}
pub fn activate(&self) -> PoaResult<()> {
self.transition(PoaManagerState::Active)
}
pub fn hold_requests(&self) -> PoaResult<()> {
self.transition(PoaManagerState::Holding)
}
pub fn discard_requests(&self) -> PoaResult<()> {
self.transition(PoaManagerState::Discarding)
}
pub fn deactivate(&self) {
self.state
.store(PoaManagerState::Inactive as u8, Ordering::Release);
}
fn transition(&self, target: PoaManagerState) -> PoaResult<()> {
loop {
let current = self.state.load(Ordering::Acquire);
if current == PoaManagerState::Inactive as u8 {
return Err(PoaError::BadInvocationOrder(
"POAManager is INACTIVE; further transitions are not allowed".into(),
));
}
if self
.state
.compare_exchange(current, target as u8, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return Ok(());
}
}
}
}
impl Default for PoaManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn new_starts_in_holding() {
let m = PoaManager::new();
assert_eq!(m.state(), PoaManagerState::Holding);
}
#[test]
fn full_state_cycle() {
let m = PoaManager::new();
m.activate().unwrap();
assert_eq!(m.state(), PoaManagerState::Active);
m.discard_requests().unwrap();
assert_eq!(m.state(), PoaManagerState::Discarding);
m.hold_requests().unwrap();
assert_eq!(m.state(), PoaManagerState::Holding);
m.activate().unwrap();
assert_eq!(m.state(), PoaManagerState::Active);
m.deactivate();
assert_eq!(m.state(), PoaManagerState::Inactive);
}
#[test]
fn inactive_is_terminal() {
let m = PoaManager::new();
m.deactivate();
assert!(matches!(m.activate(), Err(PoaError::BadInvocationOrder(_))));
assert!(matches!(
m.hold_requests(),
Err(PoaError::BadInvocationOrder(_))
));
assert!(matches!(
m.discard_requests(),
Err(PoaError::BadInvocationOrder(_))
));
}
#[test]
fn deactivate_idempotent() {
let m = PoaManager::new();
m.deactivate();
m.deactivate(); assert_eq!(m.state(), PoaManagerState::Inactive);
}
}