diff --git a/crates/xy-supervisor/src/lib.rs b/crates/xy-supervisor/src/lib.rs index e536cee..be9d9e6 100644 --- a/crates/xy-supervisor/src/lib.rs +++ b/crates/xy-supervisor/src/lib.rs @@ -1,5 +1,11 @@ //! Process-supervision primitives for the xy daemon. +pub mod backoff; pub mod child; +pub mod policy; +pub mod retry_window; +pub use backoff::Backoff; pub use child::{ChildHandle, MockChild, MockChildController}; +pub use policy::{RestartDecision, decide}; +pub use retry_window::RetryWindow; diff --git a/crates/xy-supervisor/src/policy.rs b/crates/xy-supervisor/src/policy.rs new file mode 100644 index 0000000..c2d41d9 --- /dev/null +++ b/crates/xy-supervisor/src/policy.rs @@ -0,0 +1,102 @@ +use xy_protocol::RestartPolicy; + +#[derive(Debug, PartialEq, Eq)] +pub enum RestartDecision { + Restart, + StayStopped, + MarkFailed, +} + +pub fn decide( + policy: RestartPolicy, + exit_code: Option, + retry_cap_reached: bool, +) -> RestartDecision { + let clean = matches!(exit_code, Some(0)); + let want = match policy { + RestartPolicy::Never => false, + RestartPolicy::OnFailure => !clean, + RestartPolicy::Always => true, + }; + if !want { + return RestartDecision::StayStopped; + } + if retry_cap_reached { + RestartDecision::MarkFailed + } else { + RestartDecision::Restart + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn never_never_restarts() { + assert_eq!( + decide(RestartPolicy::Never, Some(0), false), + RestartDecision::StayStopped + ); + assert_eq!( + decide(RestartPolicy::Never, Some(1), false), + RestartDecision::StayStopped + ); + assert_eq!( + decide(RestartPolicy::Never, None, false), + RestartDecision::StayStopped + ); + } + + #[test] + fn on_failure_skips_clean() { + assert_eq!( + decide(RestartPolicy::OnFailure, Some(0), false), + RestartDecision::StayStopped + ); + } + + #[test] + fn on_failure_restarts_nonzero() { + assert_eq!( + decide(RestartPolicy::OnFailure, Some(1), false), + RestartDecision::Restart + ); + } + + #[test] + fn on_failure_restarts_signal() { + assert_eq!( + decide(RestartPolicy::OnFailure, None, false), + RestartDecision::Restart + ); + } + + #[test] + fn always_restarts_on_clean() { + assert_eq!( + decide(RestartPolicy::Always, Some(0), false), + RestartDecision::Restart + ); + } + + #[test] + fn cap_reached_marks_failed() { + assert_eq!( + decide(RestartPolicy::Always, Some(0), true), + RestartDecision::MarkFailed + ); + assert_eq!( + decide(RestartPolicy::OnFailure, Some(1), true), + RestartDecision::MarkFailed + ); + } + + #[test] + fn cap_reached_never_still_stopped() { + assert_eq!( + decide(RestartPolicy::Never, Some(1), true), + RestartDecision::StayStopped + ); + } +}