use alloc::string::String;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[cfg(test)]
use super::ModusRestitutio;
use super::{
ActioRestitutio, InfansCurrens, InfansSpec, OrdoTerminatio, RestartDecision, StatusInfantis,
StrategiaSupervisionis, SupervisioError, SupervisioResult,
};
static ARBOR_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ArborId(u64);
impl ArborId {
#[inline]
pub fn new() -> Self {
ArborId(ARBOR_COUNTER.fetch_add(1, Ordering::Relaxed))
}
#[inline]
pub fn value(&self) -> u64 {
self.0
}
}
impl Default for ArborId {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Display for ArborId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Arbor({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatusArbor {
Initians,
Currens,
Terminans,
Terminatus,
}
pub struct Arbor {
id: ArborId,
name: String,
strategy: StrategiaSupervisionis,
children_specs: Vec<InfansSpec>,
children: Vec<InfansCurrens>,
status: StatusArbor,
shutdown_order: OrdoTerminatio,
stopping: AtomicBool,
}
impl Arbor {
pub fn new(name: impl Into<String>) -> Self {
Arbor {
id: ArborId::new(),
name: name.into(),
strategy: StrategiaSupervisionis::default(),
children_specs: Vec::with_capacity(4),
children: Vec::with_capacity(4),
status: StatusArbor::Initians,
shutdown_order: OrdoTerminatio::default(),
stopping: AtomicBool::new(false),
}
}
#[inline]
pub fn with_strategy(mut self, strategy: StrategiaSupervisionis) -> Self {
self.strategy = strategy;
self
}
#[inline]
pub fn with_shutdown_order(mut self, order: OrdoTerminatio) -> Self {
self.shutdown_order = order;
self
}
#[inline]
pub fn add_child(mut self, spec: InfansSpec) -> Self {
self.children_specs.push(spec);
self
}
#[inline]
pub fn add_children(mut self, specs: impl IntoIterator<Item = InfansSpec>) -> Self {
self.children_specs.extend(specs);
self
}
#[inline]
pub fn id(&self) -> ArborId {
self.id
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn status(&self) -> StatusArbor {
self.status
}
#[inline]
pub fn child_count(&self) -> usize {
self.children_specs.len()
}
#[inline]
pub fn strategy(&self) -> &StrategiaSupervisionis {
&self.strategy
}
#[inline]
pub fn children_specs(&self) -> &[InfansSpec] {
&self.children_specs
}
#[inline]
pub fn children(&self) -> &[InfansCurrens] {
&self.children
}
#[inline]
pub fn is_stopping(&self) -> bool {
self.stopping.load(Ordering::Acquire)
}
#[inline]
pub fn request_stop(&self) {
self.stopping.store(true, Ordering::Release);
}
pub fn initialize(&mut self) -> SupervisioResult<()> {
let mut specs = self.children_specs.clone();
specs.sort_by_key(super::infans::InfansSpec::priority);
self.children = specs.into_iter().map(InfansCurrens::new).collect();
self.status = StatusArbor::Currens;
Ok(())
}
pub fn handle_failure(
&mut self,
child_index: usize,
current_time: u64,
normal_exit: bool,
) -> SupervisioResult<ActioRestitutio> {
if child_index >= self.children.len() {
return Err(SupervisioError::Alius("invalid child index".into()));
}
if !self.children[child_index]
.spec()
.modus()
.should_restart(normal_exit)
{
self.children[child_index].mark_terminated();
return Ok(ActioRestitutio::Ignorare);
}
self.children[child_index].mark_failed();
let decision = self
.strategy
.decide(child_index, self.children.len(), current_time);
match decision {
RestartDecision::Restart(indices) => {
for &idx in &indices {
if idx < self.children.len() {
self.children[idx].record_restart(current_time);
}
}
Ok(ActioRestitutio::Restituere)
}
RestartDecision::Escalate => {
let child = &self.children[child_index];
Err(SupervisioError::IntensitasExcedit {
child_id: child.spec().id().into(),
restarts: child.restart_count(),
window_secs: self.strategy.window_secs().unwrap_or(60),
})
}
RestartDecision::Terminate => {
self.status = StatusArbor::Terminans;
Ok(ActioRestitutio::Terminare)
}
RestartDecision::Ignore => Ok(ActioRestitutio::Ignorare),
}
}
pub fn shutdown_indices(&self) -> Vec<usize> {
let count = self.children.len();
match self.shutdown_order {
OrdoTerminatio::Primus => (0..count).collect(),
OrdoTerminatio::Ultimus => (0..count).rev().collect(),
OrdoTerminatio::Simul => (0..count).collect(),
}
}
#[inline]
pub fn mark_child_running(&mut self, index: usize) {
if index < self.children.len() {
self.children[index].mark_running();
}
}
#[inline]
pub fn mark_child_terminated(&mut self, index: usize) {
if index < self.children.len() {
self.children[index].mark_terminated();
}
}
#[inline]
pub fn all_children_running(&self) -> bool {
self.children
.iter()
.all(|c| c.status() == StatusInfantis::Currens)
}
#[inline]
pub fn all_children_terminated(&self) -> bool {
self.children
.iter()
.all(|c| c.status() == StatusInfantis::Terminatus)
}
pub fn status_summary(&self) -> ArborSummary {
let mut running = 0;
let mut failed = 0;
let mut restarting = 0;
let mut terminated = 0;
for child in &self.children {
match child.status() {
StatusInfantis::Currens => running += 1,
StatusInfantis::Defectus => failed += 1,
StatusInfantis::Restituens => restarting += 1,
StatusInfantis::Terminatus => terminated += 1,
StatusInfantis::Incipiens => {}
}
}
ArborSummary {
total: self.children.len(),
running,
failed,
restarting,
terminated,
}
}
}
impl core::fmt::Debug for Arbor {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Arbor")
.field("id", &self.id)
.field("name", &self.name)
.field("status", &self.status)
.field("children", &self.children.len())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ArborSummary {
pub total: usize,
pub running: usize,
pub failed: usize,
pub restarting: usize,
pub terminated: usize,
}
impl ArborSummary {
#[inline]
pub fn is_healthy(&self) -> bool {
self.running == self.total && self.failed == 0
}
#[inline]
pub fn health_percentage(&self) -> f64 {
if self.total == 0 {
100.0
} else {
(self.running as f64 / self.total as f64) * 100.0
}
}
}
pub struct ArborBuilder {
arbor: Arbor,
}
impl ArborBuilder {
pub fn new(name: impl Into<String>) -> Self {
ArborBuilder {
arbor: Arbor::new(name),
}
}
pub fn strategy(mut self, strategy: StrategiaSupervisionis) -> Self {
self.arbor.strategy = strategy;
self
}
pub fn shutdown_order(mut self, order: OrdoTerminatio) -> Self {
self.arbor.shutdown_order = order;
self
}
pub fn worker(mut self, id: impl Into<String>) -> Self {
self.arbor.children_specs.push(InfansSpec::worker(id));
self
}
pub fn supervisor(mut self, id: impl Into<String>) -> Self {
self.arbor.children_specs.push(InfansSpec::supervisor(id));
self
}
pub fn child(mut self, spec: InfansSpec) -> Self {
self.arbor.children_specs.push(spec);
self
}
pub fn build(self) -> Arbor {
self.arbor
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test_arbor_id_unique() {
let id1 = ArborId::new();
let id2 = ArborId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_arbor_new() {
let arbor = Arbor::new("test_supervisor");
assert_eq!(arbor.name(), "test_supervisor");
assert_eq!(arbor.status(), StatusArbor::Initians);
assert_eq!(arbor.child_count(), 0);
}
#[test]
fn test_arbor_add_children() {
let arbor = Arbor::new("supervisor")
.add_child(InfansSpec::worker("worker1"))
.add_child(InfansSpec::worker("worker2"));
assert_eq!(arbor.child_count(), 2);
}
#[test]
fn test_arbor_builder() {
let arbor = ArborBuilder::new("supervisor")
.strategy(StrategiaSupervisionis::simplex())
.worker("worker1")
.worker("worker2")
.build();
assert_eq!(arbor.child_count(), 2);
assert_eq!(arbor.strategy().name(), "Simplex");
}
#[test]
fn test_arbor_initialize() {
let mut arbor = Arbor::new("supervisor")
.add_child(InfansSpec::worker("worker1"))
.add_child(InfansSpec::worker("worker2"));
arbor
.initialize()
.expect("arbor with two workers should initialize without error");
assert_eq!(arbor.status(), StatusArbor::Currens);
assert_eq!(arbor.children().len(), 2);
}
#[test]
fn test_arbor_handle_failure() {
let mut arbor = Arbor::new("supervisor")
.with_strategy(StrategiaSupervisionis::simplex())
.add_child(InfansSpec::worker("worker1"));
arbor
.initialize()
.expect("arbor with one worker should initialize without error");
let action = arbor
.handle_failure(0, 1000, false)
.expect("simplex strategy should return a restart action for a valid child index");
assert_eq!(action, ActioRestitutio::Restituere);
}
#[test]
fn temporarius_child_is_not_restarted() {
let mut arbor = Arbor::new("supervisor")
.with_strategy(StrategiaSupervisionis::simplex())
.add_child(InfansSpec::worker("worker1").with_modus(ModusRestitutio::Temporarius));
arbor
.initialize()
.expect("arbor with one worker should initialize without error");
let actio = arbor.handle_failure(0, 0, false).unwrap();
assert_eq!(actio, ActioRestitutio::Ignorare);
}
#[test]
fn transiens_child_normal_vs_abnormal() {
let mut arbor = Arbor::new("supervisor")
.with_strategy(StrategiaSupervisionis::simplex())
.add_child(InfansSpec::worker("worker1").with_modus(ModusRestitutio::Transiens));
arbor
.initialize()
.expect("arbor with one worker should initialize without error");
assert_eq!(
arbor.handle_failure(0, 0, true).unwrap(),
ActioRestitutio::Ignorare
);
let mut arbor = Arbor::new("supervisor")
.with_strategy(StrategiaSupervisionis::simplex())
.add_child(InfansSpec::worker("worker1").with_modus(ModusRestitutio::Transiens));
arbor
.initialize()
.expect("arbor with one worker should initialize without error");
assert_eq!(
arbor.handle_failure(0, 0, false).unwrap(),
ActioRestitutio::Restituere
);
}
#[test]
fn test_arbor_summary() {
let mut arbor = Arbor::new("supervisor")
.add_child(InfansSpec::worker("worker1"))
.add_child(InfansSpec::worker("worker2"))
.add_child(InfansSpec::worker("worker3"));
arbor
.initialize()
.expect("arbor with three workers should initialize without error");
arbor.mark_child_running(0);
arbor.mark_child_running(1);
let summary = arbor.status_summary();
assert_eq!(summary.total, 3);
assert_eq!(summary.running, 2);
}
#[test]
fn test_arbor_shutdown_order() {
let mut arbor = Arbor::new("supervisor")
.with_shutdown_order(OrdoTerminatio::Ultimus)
.add_child(InfansSpec::worker("worker1"))
.add_child(InfansSpec::worker("worker2"))
.add_child(InfansSpec::worker("worker3"));
arbor.initialize().expect(
"arbor with three workers and Ultimus shutdown order should initialize without error",
);
let indices = arbor.shutdown_indices();
assert_eq!(indices, vec![2, 1, 0]);
}
}