#![no_std] //! Deterministic INIT dependency order and retained rollback ownership. use turkos_os_init_manifest::{Entry, Validated}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rule { pub id: u16, pub after: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cause { Unavailable, Busy, Injected } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { RuleMismatch(usize), MissingDependency { service: u16, dependency: u16 }, SelfDependency(u16), Cycle, RecoveryRequired, Start { service: u16, cause: Cause }, Stop { service: u16, cause: Cause }, } pub trait Adapter { type Handle; fn start(&mut self, entry: &Entry) -> Result; /// An error leaves `handle` owned and retriable by the supervisor. fn stop(&mut self, handle: &mut Self::Handle) -> Result<(), Cause>; } /// A validated manifest is moved into a plan exactly once. pub struct Plan { entries: [Entry; N], order: [usize; N], } impl Plan { pub fn compile(manifest: Validated, rules: [Rule; N]) -> Result { let entries = manifest.into_entries(); for index in 0..N { if rules[index].id != entries[index].id { return Err(Error::RuleMismatch(index)); } if let Some(dependency) = rules[index].after { if dependency == rules[index].id { return Err(Error::SelfDependency(dependency)); } if !entries.iter().any(|entry| entry.id == dependency) { return Err(Error::MissingDependency { service: rules[index].id, dependency, }); } } } let mut order = [0usize; N]; let mut chosen = [false; N]; for position in 0..N { let next = (0..N).find(|&index| { if chosen[index] { return false; } match rules[index].after { None => true, Some(dependency) => (0..N).any(|previous| { chosen[previous] && entries[previous].id == dependency }), } }).ok_or(Error::Cycle)?; chosen[next] = true; order[position] = next; } Ok(Self {entries, order}) } pub fn order(&self) -> [u16; N] { core::array::from_fn(|position| self.entries[self.order[position]].id) } } /// Owns each successfully started handle until a successful reverse stop. #[must_use] pub struct Supervisor { plan: Plan, handles: [Option; N], started: usize, recovering: bool, } impl Supervisor { pub fn new(plan: Plan) -> Self { Self {plan, handles: core::array::from_fn(|_| None), started: 0, recovering: false} } pub fn owner_count(&self) -> usize { self.handles.iter().filter(|handle| handle.is_some()).count() } pub fn recovering(&self) -> bool { self.recovering } pub fn start_next>(&mut self, adapter: &mut A) -> Result, Error> { if self.recovering { return Err(Error::RecoveryRequired); } if self.started == N { return Ok(None); } let index = self.plan.order[self.started]; let entry = &self.plan.entries[index]; match adapter.start(entry) { Ok(handle) => { self.handles[index] = Some(handle); self.started += 1; Ok(Some(entry.id)) } Err(cause) => { self.recovering = true; Err(Error::Start {service: entry.id, cause}) } } } pub fn begin_shutdown(&mut self) { self.recovering = true; } pub fn rollback_next>(&mut self, adapter: &mut A) -> Result, Error> { if !self.recovering { return Err(Error::RecoveryRequired); } if self.started == 0 { self.recovering = false; return Ok(None); } let index = self.plan.order[self.started - 1]; let service = self.plan.entries[index].id; let handle = self.handles[index].as_mut().expect("started owner present"); adapter.stop(handle).map_err(|cause| Error::Stop {service, cause})?; self.handles[index] = None; self.started -= 1; Ok(Some(service)) } }