use core::time::Duration;
use std::fmt;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum SupervisorError {
#[error("child capacity exceeded: max={0}")]
CapacityExceeded(usize),
#[error("child not found: id={0}")]
ChildNotFound(u64),
#[error("invalid state transition: from={from:?} to={to:?}")]
InvalidTransition {
from: SupervisorState,
to: SupervisorState,
},
#[error("already in state: {0:?}")]
AlreadyInState(SupervisorState),
#[error("restart threshold exceeded: count={count}, max={max}")]
RestartThresholdExceeded {
count: u32,
max: u32,
},
#[error("supervisor is shutting down")]
ShuttingDown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SupervisorState {
Running,
Stopped,
Degraded,
Failed,
}
impl SupervisorState {
pub fn can_transition_to(self, to: SupervisorState) -> bool {
use SupervisorState::*;
matches!(
(self, to),
(Running, Stopped)
| (Running, Degraded)
| (Running, Failed)
| (Degraded, Running)
| (Degraded, Stopped)
| (Degraded, Failed)
| (Failed, Running)
| (Stopped, Running)
)
}
}
impl fmt::Display for SupervisorState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SupervisorState::Running => write!(f, "Running"),
SupervisorState::Stopped => write!(f, "Stopped"),
SupervisorState::Degraded => write!(f, "Degraded"),
SupervisorState::Failed => write!(f, "Failed"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChildKind {
Domain,
Queue,
Worker,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RestartStrategy {
Permanent,
Transient,
Temporary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExitReason {
Normal,
Abnormal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackoffConfig {
pub initial: Duration,
pub max: Duration,
pub multiplier: u32,
pub max_restarts: u32,
}
impl Default for BackoffConfig {
fn default() -> Self {
Self {
initial: Duration::from_millis(10),
max: Duration::from_secs(5),
multiplier: 2,
max_restarts: 5,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ChildEntry {
pub id: u64,
pub kind: ChildKind,
pub state: SupervisorState,
pub restart_count: u32,
pub last_healthy: bool,
pub(crate) occupied: bool,
}
impl ChildEntry {
const fn empty() -> Self {
Self {
id: 0,
kind: ChildKind::Worker,
state: SupervisorState::Stopped,
restart_count: 0,
last_healthy: true,
occupied: false,
}
}
pub fn is_alive(&self) -> bool {
self.occupied
&& matches!(self.state, SupervisorState::Running | SupervisorState::Degraded)
}
}
#[derive(Debug, Clone, Copy)]
pub struct HealthReport {
pub total: usize,
pub healthy: usize,
pub degraded: usize,
pub failed: usize,
}
impl HealthReport {
pub fn is_healthy(&self) -> bool {
self.failed == 0 && self.degraded == 0
}
}
#[derive(Debug)]
pub struct ChildSet<const N: usize> {
slots: [ChildEntry; N],
count: usize,
}
impl<const N: usize> Default for ChildSet<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> ChildSet<N> {
pub const fn new() -> Self {
Self {
slots: [const { ChildEntry::empty() }; N],
count: 0,
}
}
#[inline]
pub fn len(&self) -> usize {
self.count
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
pub const fn capacity(&self) -> usize {
N
}
pub fn iter(&self) -> impl Iterator<Item = &ChildEntry> {
self.slots.iter().filter(|s| s.occupied)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ChildEntry> {
self.slots.iter_mut().filter(|s| s.occupied)
}
pub fn get(&self, id: u64) -> Option<&ChildEntry> {
self.slots.iter().find(|s| s.occupied && s.id == id)
}
pub fn get_mut(&mut self, id: u64) -> Option<&mut ChildEntry> {
self.slots.iter_mut().find(|s| s.occupied && s.id == id)
}
pub fn spawn(&mut self, entry: ChildEntry) -> Result<(), SupervisorError> {
if self.count >= N {
return Err(SupervisorError::CapacityExceeded(N));
}
if self.slots.iter().any(|s| s.occupied && s.id == entry.id) {
return Err(SupervisorError::AlreadyInState(SupervisorState::Running));
}
let slot = self
.slots
.iter_mut()
.find(|s| !s.occupied)
.ok_or(SupervisorError::CapacityExceeded(N))?;
*slot = entry;
self.count += 1;
Ok(())
}
pub fn stop(&mut self, id: u64) -> Result<(), SupervisorError> {
let slot = self
.slots
.iter_mut()
.find(|s| s.occupied && s.id == id)
.ok_or(SupervisorError::ChildNotFound(id))?;
slot.state = SupervisorState::Stopped;
slot.occupied = false;
slot.restart_count = 0;
self.count -= 1;
Ok(())
}
pub fn health_report(&self) -> HealthReport {
let mut report = HealthReport {
total: self.count,
healthy: 0,
degraded: 0,
failed: 0,
};
for slot in self.slots.iter().filter(|s| s.occupied) {
match slot.state {
SupervisorState::Running if slot.last_healthy => report.healthy += 1,
SupervisorState::Running => report.degraded += 1,
SupervisorState::Degraded => report.degraded += 1,
SupervisorState::Failed => report.failed += 1,
SupervisorState::Stopped => {}
}
}
report
}
#[inline]
pub fn all_healthy(&self) -> bool {
self.slots
.iter()
.filter(|s| s.occupied)
.all(|s| s.state == SupervisorState::Running && s.last_healthy)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SupervisorConfig {
pub initial_state: SupervisorState,
pub restart_strategy: RestartStrategy,
pub backoff: BackoffConfig,
}
impl Default for SupervisorConfig {
fn default() -> Self {
Self {
initial_state: SupervisorState::Running,
restart_strategy: RestartStrategy::Permanent,
backoff: BackoffConfig::default(),
}
}
}
#[derive(Debug)]
pub struct SupervisorCore<const N: usize> {
state: SupervisorState,
config: SupervisorConfig,
children: ChildSet<N>,
total_restarts: u64,
fuse_threshold: u64,
}
impl<const N: usize> SupervisorCore<N> {
pub fn new(config: SupervisorConfig) -> Self {
Self {
state: config.initial_state,
config,
children: ChildSet::new(),
total_restarts: 0,
fuse_threshold: 100,
}
}
#[inline]
pub fn state(&self) -> SupervisorState {
self.state
}
#[inline]
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn with_fuse_threshold(mut self, threshold: u64) -> Self {
self.fuse_threshold = threshold;
self
}
#[inline]
pub fn get_child(&self, id: u64) -> Option<&ChildEntry> {
self.children.get(id)
}
fn next_backoff(&self, restart_count: u32) -> Duration {
let base_ns = self.config.backoff.initial.as_nanos() as u64;
let mult = u64::from(self.config.backoff.multiplier);
let max_ns = self.config.backoff.max.as_nanos() as u64;
Duration::from_nanos(zenith_foundation::backoff::exponential_backoff_with_jitter(
base_ns,
mult,
restart_count,
max_ns,
))
}
pub fn spawn_child(
&mut self,
id: u64,
kind: ChildKind,
) -> Result<(), SupervisorError> {
if self.state == SupervisorState::Failed {
return Err(SupervisorError::ShuttingDown);
}
if self.state == SupervisorState::Stopped {
return Err(SupervisorError::ShuttingDown);
}
let entry = ChildEntry {
id,
kind,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
};
self.children.spawn(entry)?;
Ok(())
}
pub fn stop_child(&mut self, id: u64) -> Result<(), SupervisorError> {
self.children.stop(id)?;
Ok(())
}
pub fn health_check_child(&mut self, id: u64, healthy: bool) -> Result<(), SupervisorError> {
{
let child = self
.children
.get_mut(id)
.ok_or(SupervisorError::ChildNotFound(id))?;
child.last_healthy = healthy;
if healthy {
if child.state == SupervisorState::Degraded {
child.state = SupervisorState::Running;
}
} else if child.state == SupervisorState::Running {
child.state = SupervisorState::Degraded;
}
}
self.update_self_state();
Ok(())
}
pub fn health_check(&mut self) -> HealthReport {
self.update_self_state();
self.children.health_report()
}
fn update_self_state(&mut self) {
if self.state == SupervisorState::Stopped || self.state == SupervisorState::Failed {
return;
}
let report = self.children.health_report();
if report.failed == report.total && report.total > 0 {
let _ = self.transition(SupervisorState::Failed);
} else if report.failed > 0 || report.degraded > 0 {
let _ = self.transition(SupervisorState::Degraded);
} else if report.total == 0 || report.healthy == report.total {
let _ = self.transition(SupervisorState::Running);
}
}
fn transition(&mut self, to: SupervisorState) -> Result<(), SupervisorError> {
if self.state == to {
return Ok(());
}
if !self.state.can_transition_to(to) {
return Err(SupervisorError::InvalidTransition { from: self.state, to });
}
self.state = to;
Ok(())
}
pub fn restart(&mut self, id: u64, reason: ExitReason) -> Result<Duration, SupervisorError> {
if self.state == SupervisorState::Failed {
return Err(SupervisorError::ShuttingDown);
}
match self.config.restart_strategy {
RestartStrategy::Temporary => {
self.children.stop(id)?;
return Ok(Duration::ZERO);
}
RestartStrategy::Transient if reason == ExitReason::Normal => {
self.children.stop(id)?;
return Ok(Duration::ZERO);
}
RestartStrategy::Permanent | RestartStrategy::Transient => {
}
}
let current_restart_count = {
let child = self
.children
.get_mut(id)
.ok_or(SupervisorError::ChildNotFound(id))?;
let current = child.restart_count;
child.restart_count = child.restart_count.saturating_add(1);
self.total_restarts = self.total_restarts.saturating_add(1);
if self.total_restarts > self.fuse_threshold {
let _ = self.transition(SupervisorState::Failed);
return Err(SupervisorError::RestartThresholdExceeded {
count: self.total_restarts as u32,
max: self.fuse_threshold as u32,
});
}
current
};
let backoff = self.next_backoff(current_restart_count);
if let Some(c) = self.children.get_mut(id) {
c.state = SupervisorState::Running;
c.last_healthy = true;
}
Ok(backoff)
}
pub fn shutdown(&mut self) {
for slot in self.children.iter_mut() {
slot.state = SupervisorState::Stopped;
slot.occupied = false;
}
self.children.count = 0;
let _ = self.transition(SupervisorState::Stopped);
}
pub fn resume(&mut self) -> Result<(), SupervisorError> {
self.transition(SupervisorState::Running)
}
}
#[derive(Debug)]
pub struct QueueSupervisor {
core: SupervisorCore<8>,
queue_id: u32,
}
impl QueueSupervisor {
pub fn new(queue_id: u32) -> Self {
Self {
core: SupervisorCore::new(SupervisorConfig::default()),
queue_id,
}
}
pub fn with_config(queue_id: u32, config: SupervisorConfig) -> Self {
Self {
core: SupervisorCore::new(config),
queue_id,
}
}
#[inline]
pub fn queue_id(&self) -> u32 {
self.queue_id
}
#[inline]
pub fn worker_count(&self) -> usize {
self.core.child_count()
}
#[inline]
pub fn state(&self) -> SupervisorState {
self.core.state()
}
pub fn spawn_worker(&mut self, id: u64) -> Result<(), SupervisorError> {
self.core.spawn_child(id, ChildKind::Worker)
}
pub fn stop_worker(&mut self, id: u64) -> Result<(), SupervisorError> {
self.core.stop_child(id)
}
pub fn health_check_worker(
&mut self,
id: u64,
healthy: bool,
) -> Result<(), SupervisorError> {
self.core.health_check_child(id, healthy)
}
pub fn health_check(&mut self) -> HealthReport {
self.core.health_check()
}
pub fn restart_worker(&mut self, id: u64, reason: ExitReason) -> Result<Duration, SupervisorError> {
self.core.restart(id, reason)
}
pub fn shutdown(&mut self) {
self.core.shutdown();
}
}
#[derive(Debug)]
pub struct DomainSupervisor {
core: SupervisorCore<16>,
queues: ChildSet<16>,
domain_id: u32,
}
impl DomainSupervisor {
pub fn new(domain_id: u32) -> Self {
Self {
core: SupervisorCore::new(SupervisorConfig::default()),
queues: ChildSet::new(),
domain_id,
}
}
#[inline]
pub fn domain_id(&self) -> u32 {
self.domain_id
}
#[inline]
pub fn state(&self) -> SupervisorState {
self.core.state()
}
pub fn spawn_queue(&mut self, queue_id: u64) -> Result<(), SupervisorError> {
let entry = ChildEntry {
id: queue_id,
kind: ChildKind::Queue,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
};
self.core.spawn_child(queue_id, ChildKind::Queue)?;
if let Err(e) = self.queues.spawn(entry) {
let _ = self.core.stop_child(queue_id);
return Err(e);
}
Ok(())
}
pub fn stop_queue(&mut self, queue_id: u64) -> Result<(), SupervisorError> {
self.core.stop_child(queue_id)?;
if let Err(e) = self.queues.stop(queue_id) {
let _ = self.core.spawn_child(queue_id, ChildKind::Queue);
return Err(e);
}
Ok(())
}
pub fn mark_queue_healthy(&mut self, queue_id: u64, healthy: bool) -> Result<(), SupervisorError> {
let q = self
.queues
.get_mut(queue_id)
.ok_or(SupervisorError::ChildNotFound(queue_id))?;
q.last_healthy = healthy;
q.state = if healthy {
SupervisorState::Running
} else {
SupervisorState::Degraded
};
self.core.health_check_child(queue_id, healthy)?;
Ok(())
}
pub fn health_check(&mut self) -> HealthReport {
self.core.health_check()
}
pub fn restart_queue(&mut self, queue_id: u64, reason: ExitReason) -> Result<Duration, SupervisorError> {
self.core.restart(queue_id, reason)
}
pub fn shutdown(&mut self) {
self.core.shutdown();
for slot in self.queues.iter_mut() {
slot.state = SupervisorState::Stopped;
slot.occupied = false;
slot.restart_count = 0;
slot.last_healthy = true;
}
self.queues.count = 0;
}
}
#[derive(Debug)]
pub struct NodeSupervisor {
core: SupervisorCore<8>,
domains: ChildSet<8>,
node_id: u64,
}
impl NodeSupervisor {
pub fn new(node_id: u64) -> Self {
Self {
core: SupervisorCore::new(SupervisorConfig::default())
.with_fuse_threshold(1000),
domains: ChildSet::new(),
node_id,
}
}
#[inline]
pub fn node_id(&self) -> u64 {
self.node_id
}
#[inline]
pub fn state(&self) -> SupervisorState {
self.core.state()
}
pub fn spawn_domain(&mut self, domain_id: u64) -> Result<(), SupervisorError> {
let entry = ChildEntry {
id: domain_id,
kind: ChildKind::Domain,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
};
self.core.spawn_child(domain_id, ChildKind::Domain)?;
if let Err(e) = self.domains.spawn(entry) {
let _ = self.core.stop_child(domain_id);
return Err(e);
}
Ok(())
}
pub fn stop_domain(&mut self, domain_id: u64) -> Result<(), SupervisorError> {
self.core.stop_child(domain_id)?;
if let Err(e) = self.domains.stop(domain_id) {
let _ = self.core.spawn_child(domain_id, ChildKind::Domain);
return Err(e);
}
Ok(())
}
pub fn mark_domain_healthy(
&mut self,
domain_id: u64,
healthy: bool,
) -> Result<(), SupervisorError> {
let d = self
.domains
.get_mut(domain_id)
.ok_or(SupervisorError::ChildNotFound(domain_id))?;
d.last_healthy = healthy;
d.state = if healthy {
SupervisorState::Running
} else {
SupervisorState::Degraded
};
self.core.health_check_child(domain_id, healthy)?;
Ok(())
}
pub fn health_check(&mut self) -> HealthReport {
self.core.health_check()
}
pub fn restart_domain(&mut self, domain_id: u64, reason: ExitReason) -> Result<Duration, SupervisorError> {
self.core.restart(domain_id, reason)
}
pub fn shutdown(&mut self) {
self.core.shutdown();
for slot in self.domains.iter_mut() {
slot.state = SupervisorState::Stopped;
slot.occupied = false;
slot.restart_count = 0;
slot.last_healthy = true;
}
self.domains.count = 0;
}
#[inline]
pub fn is_healthy(&self) -> bool {
self.domains.all_healthy()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_supervisor_state_transitions() {
assert!(SupervisorState::Running.can_transition_to(SupervisorState::Stopped));
assert!(SupervisorState::Running.can_transition_to(SupervisorState::Degraded));
assert!(SupervisorState::Degraded.can_transition_to(SupervisorState::Running));
assert!(SupervisorState::Degraded.can_transition_to(SupervisorState::Failed));
assert!(SupervisorState::Stopped.can_transition_to(SupervisorState::Running));
assert!(!SupervisorState::Running.can_transition_to(SupervisorState::Running));
}
#[test]
fn test_child_set_spawn_and_stop() {
let mut set: ChildSet<4> = ChildSet::new();
assert_eq!(set.len(), 0);
let entry = ChildEntry {
id: 1,
kind: ChildKind::Worker,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
};
set.spawn(entry).unwrap();
assert_eq!(set.len(), 1);
assert!(set.get(1).is_some());
set.stop(1).unwrap();
assert_eq!(set.len(), 0);
}
#[test]
fn test_child_set_capacity_exceeded() {
let mut set: ChildSet<2> = ChildSet::new();
for id in 0..2 {
let entry = ChildEntry {
id,
kind: ChildKind::Worker,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
};
set.spawn(entry).unwrap();
}
let overflow = ChildEntry {
id: 99,
kind: ChildKind::Worker,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
};
assert!(set.spawn(overflow).is_err());
}
#[test]
fn test_child_set_health_report() {
let mut set: ChildSet<4> = ChildSet::new();
for id in 0..3 {
set.spawn(ChildEntry {
id,
kind: ChildKind::Worker,
state: SupervisorState::Running,
restart_count: 0,
last_healthy: true,
occupied: true,
})
.unwrap();
}
let mut report = set.health_report();
assert_eq!(report.total, 3);
assert_eq!(report.healthy, 3);
set.get_mut(1).unwrap().state = SupervisorState::Failed;
report = set.health_report();
assert_eq!(report.failed, 1);
assert_eq!(report.healthy, 2);
}
#[test]
fn test_queue_supervisor_spawn_and_health() {
let mut q = QueueSupervisor::new(1);
assert_eq!(q.state(), SupervisorState::Running);
q.spawn_worker(10).unwrap();
q.spawn_worker(11).unwrap();
let report = q.health_check();
assert!(report.is_healthy());
q.health_check_worker(11, false).unwrap();
let report = q.health_check();
assert_eq!(report.degraded, 1);
}
#[test]
fn test_queue_supervisor_restart() {
let mut q = QueueSupervisor::new(2);
q.spawn_worker(20).unwrap();
let backoff = q.restart_worker(20, ExitReason::Abnormal).unwrap();
assert!(backoff <= Duration::from_millis(10));
let backoff = q.restart_worker(20, ExitReason::Abnormal).unwrap();
assert!(backoff <= Duration::from_millis(20));
let backoff = q.restart_worker(20, ExitReason::Abnormal).unwrap();
assert!(backoff <= Duration::from_millis(40));
}
#[test]
fn test_queue_supervisor_shutdown() {
let mut q = QueueSupervisor::new(3);
q.spawn_worker(30).unwrap();
q.spawn_worker(31).unwrap();
q.shutdown();
assert_eq!(q.state(), SupervisorState::Stopped);
assert!(q.spawn_worker(32).is_err());
}
#[test]
fn test_domain_supervisor_lifecycle() {
let mut d = DomainSupervisor::new(100);
d.spawn_queue(1).unwrap();
d.spawn_queue(2).unwrap();
assert_eq!(d.state(), SupervisorState::Running);
d.mark_queue_healthy(2, false).unwrap();
let report = d.health_check();
assert_eq!(report.degraded, 1);
d.mark_queue_healthy(2, true).unwrap();
let report = d.health_check();
assert!(report.is_healthy());
d.stop_queue(1).unwrap();
assert_eq!(d.state(), SupervisorState::Running);
}
#[test]
fn test_node_supervisor_lifecycle() {
let mut n = NodeSupervisor::new(1);
n.spawn_domain(10).unwrap();
n.spawn_domain(20).unwrap();
assert!(n.is_healthy());
n.mark_domain_healthy(20, false).unwrap();
let report = n.health_check();
assert_eq!(report.degraded, 1);
n.mark_domain_healthy(20, true).unwrap();
assert!(n.is_healthy());
}
#[test]
fn test_restart_threshold_fuse() {
let config = SupervisorConfig {
initial_state: SupervisorState::Running,
restart_strategy: RestartStrategy::Permanent,
backoff: BackoffConfig::default(),
};
let mut core: SupervisorCore<4> = SupervisorCore::new(config).with_fuse_threshold(2);
core.spawn_child(1, ChildKind::Worker).unwrap();
let _ = core.restart(1, ExitReason::Abnormal).unwrap();
let _ = core.restart(1, ExitReason::Abnormal).unwrap();
let res = core.restart(1, ExitReason::Abnormal);
assert!(res.is_err());
assert_eq!(res.unwrap_err(), SupervisorError::RestartThresholdExceeded { count: 3, max: 2 });
assert_eq!(core.state(), SupervisorState::Failed);
}
#[test]
fn test_temporary_strategy_no_restart() {
let config = SupervisorConfig {
initial_state: SupervisorState::Running,
restart_strategy: RestartStrategy::Temporary,
backoff: BackoffConfig::default(),
};
let mut core: SupervisorCore<4> = SupervisorCore::new(config);
core.spawn_child(1, ChildKind::Worker).unwrap();
let backoff = core.restart(1, ExitReason::Abnormal).unwrap();
assert_eq!(backoff, Duration::ZERO);
assert!(core.get_child(1).is_none());
assert_eq!(core.child_count(), 0);
core.spawn_child(2, ChildKind::Worker).unwrap();
assert_eq!(core.child_count(), 1);
}
#[test]
fn test_transient_strategy_restart_on_abnormal_exit() {
let config = SupervisorConfig {
initial_state: SupervisorState::Running,
restart_strategy: RestartStrategy::Transient,
backoff: BackoffConfig::default(),
};
let mut core: SupervisorCore<4> = SupervisorCore::new(config);
core.spawn_child(1, ChildKind::Worker).unwrap();
let backoff = core.restart(1, ExitReason::Abnormal).unwrap();
assert!(backoff <= Duration::from_millis(10));
let child = core.get_child(1).expect("Transient 异常退出后子项必须保留");
assert_eq!(child.state, SupervisorState::Running);
assert_eq!(child.restart_count, 1);
}
#[test]
fn test_transient_strategy_no_restart_on_normal_exit() {
let config = SupervisorConfig {
initial_state: SupervisorState::Running,
restart_strategy: RestartStrategy::Transient,
backoff: BackoffConfig::default(),
};
let mut core: SupervisorCore<4> = SupervisorCore::new(config);
core.spawn_child(1, ChildKind::Worker).unwrap();
let backoff = core.restart(1, ExitReason::Normal).unwrap();
assert_eq!(backoff, Duration::ZERO);
assert!(core.get_child(1).is_none(), "Transient 正常退出后子项必须移除");
}
#[test]
fn test_permanent_strategy_restart_on_normal_exit() {
let config = SupervisorConfig {
initial_state: SupervisorState::Running,
restart_strategy: RestartStrategy::Permanent,
backoff: BackoffConfig::default(),
};
let mut core: SupervisorCore<4> = SupervisorCore::new(config);
core.spawn_child(1, ChildKind::Worker).unwrap();
let backoff = core.restart(1, ExitReason::Normal).unwrap();
assert!(backoff <= Duration::from_millis(10));
assert!(core.get_child(1).is_some(), "Permanent 正常退出后子项必须保留");
}
#[test]
fn test_fault_isolation() {
let mut d = DomainSupervisor::new(7);
d.spawn_queue(1).unwrap();
d.spawn_queue(2).unwrap();
d.mark_queue_healthy(1, false).unwrap();
assert_eq!(d.state(), SupervisorState::Degraded);
d.mark_queue_healthy(1, true).unwrap();
assert_eq!(d.state(), SupervisorState::Running);
}
#[test]
fn test_invalid_state_transition() {
let mut core: SupervisorCore<2> = SupervisorCore::new(SupervisorConfig::default());
core.shutdown();
let err = core.transition(SupervisorState::Degraded).unwrap_err();
assert!(matches!(
err,
SupervisorError::InvalidTransition {
from: SupervisorState::Stopped,
to: SupervisorState::Degraded,
}
));
}
}