use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum LifecycleState {
Starting = 0,
Ready = 1,
Draining = 2,
Stopped = 3,
}
impl LifecycleState {
#[must_use]
fn from_u8(v: u8) -> Self {
match v {
1 => LifecycleState::Ready,
2 => LifecycleState::Draining,
3 => LifecycleState::Stopped,
_ => LifecycleState::Starting,
}
}
#[must_use]
pub fn is_live(self) -> bool {
!matches!(self, LifecycleState::Stopped)
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
LifecycleState::Starting => "starting",
LifecycleState::Ready => "ready",
LifecycleState::Draining => "draining",
LifecycleState::Stopped => "stopped",
}
}
}
impl std::fmt::Display for LifecycleState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone)]
pub struct Lifecycle {
state: Arc<AtomicU8>,
}
impl Default for Lifecycle {
fn default() -> Self {
Self::new()
}
}
impl Lifecycle {
#[must_use]
pub fn new() -> Self {
Lifecycle {
state: Arc::new(AtomicU8::new(LifecycleState::Starting as u8)),
}
}
#[must_use]
pub fn state(&self) -> LifecycleState {
LifecycleState::from_u8(self.state.load(Ordering::Acquire))
}
#[must_use]
pub fn is_live(&self) -> bool {
self.state().is_live()
}
#[must_use]
pub fn is_ready(&self) -> bool {
self.state() == LifecycleState::Ready
}
pub fn mark_ready(&self) {
let _ = self.state.compare_exchange(
LifecycleState::Starting as u8,
LifecycleState::Ready as u8,
Ordering::AcqRel,
Ordering::Acquire,
);
}
pub fn begin_drain(&self) {
loop {
let current = self.state.load(Ordering::Acquire);
if current == LifecycleState::Draining as u8 || current == LifecycleState::Stopped as u8
{
return;
}
if self
.state
.compare_exchange(
current,
LifecycleState::Draining as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
return;
}
}
}
pub fn mark_stopped(&self) {
self.state
.store(LifecycleState::Stopped as u8, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lifecycle_transitions() {
let lc = Lifecycle::new();
assert_eq!(lc.state(), LifecycleState::Starting);
assert!(!lc.is_ready());
assert!(lc.is_live());
lc.mark_ready();
assert_eq!(lc.state(), LifecycleState::Ready);
assert!(lc.is_ready());
lc.begin_drain();
assert_eq!(lc.state(), LifecycleState::Draining);
lc.begin_drain();
assert_eq!(lc.state(), LifecycleState::Draining);
lc.mark_stopped();
assert_eq!(lc.state(), LifecycleState::Stopped);
assert!(!lc.is_live());
}
#[test]
fn mark_ready_from_draining_is_noop() {
let lc = Lifecycle::new();
lc.begin_drain();
lc.mark_ready();
assert_eq!(lc.state(), LifecycleState::Draining);
}
}