#![no_std] //! Bounded service restart with retained ownership on failed release. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cause { Unavailable, Busy, Injected } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidPolicy, WrongState, Start(Cause), Stop(Cause), ClockOverflow, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Policy { pub max_restarts: u8, pub base_delay: u64, pub max_delay: u64, } impl Policy { pub fn validate(self) -> Result { if self.max_restarts == 0 || self.max_restarts > 8 || self.base_delay == 0 || self.max_delay < self.base_delay { return Err(Error::InvalidPolicy); } Ok(self) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Phase { Empty, Running, Releasing { at: u64, restart: bool }, Backoff { ready_at: u64 }, Stopped, Exhausted, } pub trait Adapter { type Handle; fn start(&mut self) -> Result; /// An error leaves the handle owned and retriable. fn stop(&mut self, handle: &mut Self::Handle) -> Result<(), Cause>; } #[must_use] pub struct Controller { policy: Policy, phase: Phase, handle: Option, restarts_used: u8, } impl Controller { pub fn new(policy: Policy) -> Result { Ok(Self {policy: policy.validate()?, phase: Phase::Empty, handle: None, restarts_used: 0}) } pub const fn phase(&self) -> Phase { self.phase } pub const fn restarts_used(&self) -> u8 { self.restarts_used } pub fn owner_count(&self) -> usize { usize::from(self.handle.is_some()) } pub fn start_initial>(&mut self, adapter: &mut A) -> Result<(), Error> { if self.phase != Phase::Empty { return Err(Error::WrongState); } let handle = adapter.start().map_err(Error::Start)?; self.handle = Some(handle); self.phase = Phase::Running; Ok(()) } pub fn on_exit>(&mut self, adapter: &mut A, at: u64, restart: bool) -> Result<(), Error> { if self.phase != Phase::Running { return Err(Error::WrongState); } self.phase = Phase::Releasing {at, restart}; self.retry_release(adapter) } pub fn retry_release>(&mut self, adapter: &mut A) -> Result<(), Error> { let Phase::Releasing {at, restart} = self.phase else { return Err(Error::WrongState); }; let handle = self.handle.as_mut().expect("running owner present"); adapter.stop(handle).map_err(Error::Stop)?; self.handle = None; if restart { self.schedule(at) } else { self.phase = Phase::Stopped; Ok(()) } } fn schedule(&mut self, at: u64) -> Result<(), Error> { if self.restarts_used >= self.policy.max_restarts { self.phase = Phase::Exhausted; return Ok(()); } let multiplier = 1u64 << self.restarts_used; let delay = self.policy.base_delay.saturating_mul(multiplier) .min(self.policy.max_delay); let Some(ready_at) = at.checked_add(delay) else { self.phase = Phase::Exhausted; return Err(Error::ClockOverflow); }; self.phase = Phase::Backoff {ready_at}; Ok(()) } pub fn poll>(&mut self, adapter: &mut A, now: u64) -> Result { let Phase::Backoff {ready_at} = self.phase else { return Err(Error::WrongState); }; if now < ready_at { return Ok(false); } self.restarts_used += 1; match adapter.start() { Ok(handle) => { self.handle = Some(handle); self.phase = Phase::Running; Ok(true) } Err(cause) => { self.schedule(now)?; Err(Error::Start(cause)) } } } }