use serde::{Deserialize, Serialize};
use crate::message::ChildTerminated;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum SupervisionStrategy {
#[default]
OneForOne,
OneForAll,
RestForOne,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SupervisionDecision {
RestartChild,
RestartAll,
RestartFrom(usize),
NoRestart,
Escalate,
}
impl SupervisionStrategy {
#[must_use]
pub const fn decide(
&self,
notification: &ChildTerminated,
child_index: usize,
) -> SupervisionDecision {
if !notification.restart_policy.should_restart(¬ification.reason) {
return SupervisionDecision::NoRestart;
}
match self {
Self::OneForOne => SupervisionDecision::RestartChild,
Self::OneForAll => SupervisionDecision::RestartAll,
Self::RestForOne => SupervisionDecision::RestartFrom(child_index),
}
}
#[must_use]
pub const fn requires_group_restart(&self) -> bool {
matches!(self, Self::OneForAll | Self::RestForOne)
}
}
impl std::fmt::Display for SupervisionStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::OneForOne => write!(f, "one_for_one"),
Self::OneForAll => write!(f, "one_for_all"),
Self::RestForOne => write!(f, "rest_for_one"),
}
}
}
impl std::fmt::Display for SupervisionDecision {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RestartChild => write!(f, "restart child"),
Self::RestartAll => write!(f, "restart all children"),
Self::RestartFrom(idx) => write!(f, "restart from child index {idx}"),
Self::NoRestart => write!(f, "no restart"),
Self::Escalate => write!(f, "escalate to parent"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::actor::{RestartPolicy, TerminationReason};
use acton_ern::Ern;
fn make_notification(policy: RestartPolicy, reason: TerminationReason) -> ChildTerminated {
ChildTerminated::new(Ern::with_root("test-child").unwrap(), reason, policy)
}
#[test]
fn one_for_one_restarts_single_child_on_panic() {
let strategy = SupervisionStrategy::OneForOne;
let notification = make_notification(
RestartPolicy::Permanent,
TerminationReason::Panic("test".into()),
);
let decision = strategy.decide(¬ification, 0);
assert_eq!(decision, SupervisionDecision::RestartChild);
}
#[test]
fn one_for_all_restarts_all_children_on_panic() {
let strategy = SupervisionStrategy::OneForAll;
let notification = make_notification(
RestartPolicy::Permanent,
TerminationReason::Panic("test".into()),
);
let decision = strategy.decide(¬ification, 0);
assert_eq!(decision, SupervisionDecision::RestartAll);
}
#[test]
fn rest_for_one_restarts_from_index() {
let strategy = SupervisionStrategy::RestForOne;
let notification = make_notification(
RestartPolicy::Permanent,
TerminationReason::Panic("test".into()),
);
let decision = strategy.decide(¬ification, 2);
assert_eq!(decision, SupervisionDecision::RestartFrom(2));
}
#[test]
fn temporary_policy_prevents_restart_for_all_strategies() {
let notification = make_notification(
RestartPolicy::Temporary,
TerminationReason::Panic("test".into()),
);
for strategy in [
SupervisionStrategy::OneForOne,
SupervisionStrategy::OneForAll,
SupervisionStrategy::RestForOne,
] {
let decision = strategy.decide(¬ification, 0);
assert_eq!(decision, SupervisionDecision::NoRestart);
}
}
#[test]
fn transient_policy_no_restart_on_normal_termination() {
let notification = make_notification(RestartPolicy::Transient, TerminationReason::Normal);
let decision = SupervisionStrategy::OneForOne.decide(¬ification, 0);
assert_eq!(decision, SupervisionDecision::NoRestart);
}
#[test]
fn transient_policy_restarts_on_panic() {
let notification = make_notification(
RestartPolicy::Transient,
TerminationReason::Panic("test".into()),
);
let decision = SupervisionStrategy::OneForOne.decide(¬ification, 0);
assert_eq!(decision, SupervisionDecision::RestartChild);
}
#[test]
fn parent_shutdown_never_restarts() {
let notification =
make_notification(RestartPolicy::Permanent, TerminationReason::ParentShutdown);
let decision = SupervisionStrategy::OneForOne.decide(¬ification, 0);
assert_eq!(decision, SupervisionDecision::NoRestart);
}
#[test]
fn requires_group_restart_for_one_for_all() {
assert!(!SupervisionStrategy::OneForOne.requires_group_restart());
assert!(SupervisionStrategy::OneForAll.requires_group_restart());
assert!(SupervisionStrategy::RestForOne.requires_group_restart());
}
#[test]
fn default_strategy_is_one_for_one() {
assert_eq!(
SupervisionStrategy::default(),
SupervisionStrategy::OneForOne
);
}
}