use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, PoisonError};
use std::time::Instant;
use crate::info::Lane;
pub trait ControlLine: Send + Sync + 'static {
fn epoch(&self) -> u64;
fn state(&self) -> ControlState;
fn deadline(&self) -> Option<Instant>;
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ControlState {
#[default]
Normal,
Shed {
from_lane: Lane,
},
Lockdown,
Draining,
Stopping,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoControl;
impl ControlLine for NoControl {
#[inline]
fn epoch(&self) -> u64 {
0
}
fn state(&self) -> ControlState {
ControlState::Normal
}
fn deadline(&self) -> Option<Instant> {
None
}
}
#[derive(Debug, Default)]
pub struct SimpleControl {
epoch: AtomicU64,
inner: Mutex<(ControlState, Option<Instant>)>,
}
impl SimpleControl {
#[must_use]
pub fn new() -> SimpleControl {
SimpleControl::default()
}
pub fn set(&self, state: ControlState, deadline: Option<Instant>) {
*self.inner.lock().unwrap_or_else(PoisonError::into_inner) = (state, deadline);
self.epoch.fetch_add(1, Ordering::Release);
}
}
impl ControlLine for SimpleControl {
#[inline]
fn epoch(&self) -> u64 {
self.epoch.load(Ordering::Acquire)
}
fn state(&self) -> ControlState {
self.inner.lock().unwrap_or_else(PoisonError::into_inner).0
}
fn deadline(&self) -> Option<Instant> {
self.inner.lock().unwrap_or_else(PoisonError::into_inner).1
}
}
#[derive(Debug)]
pub(crate) struct ControlView {
epoch: u64,
state: ControlState,
deadline: Option<Instant>,
}
impl ControlView {
pub(crate) fn new() -> ControlView {
ControlView {
epoch: 0,
state: ControlState::Normal,
deadline: None,
}
}
#[inline]
pub(crate) fn refresh(&mut self, line: &dyn ControlLine) -> bool {
let epoch = line.epoch();
if epoch == self.epoch {
return false;
}
self.epoch = epoch;
let state = line.state();
self.deadline = line.deadline();
let changed = state != self.state;
self.state = state;
changed
}
pub(crate) fn state(&self) -> ControlState {
self.state
}
pub(crate) fn deadline(&self) -> Option<Instant> {
self.deadline
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_view_sees_only_final_state() {
let line = SimpleControl::new();
let mut view = ControlView::new();
assert!(!view.refresh(&line));
line.set(ControlState::Shed { from_lane: Lane(1) }, None);
line.set(ControlState::Normal, None);
assert!(!view.refresh(&line));
assert_eq!(view.state(), ControlState::Normal);
line.set(ControlState::Draining, None);
assert!(view.refresh(&line));
assert_eq!(view.state(), ControlState::Draining);
}
#[test]
fn test_no_control_never_changes() {
let mut view = ControlView::new();
assert!(!view.refresh(&NoControl));
}
}