Skip to main content

mod_signal/
reason.rs

1//! [`ShutdownReason`] taxonomy.
2
3use core::fmt;
4
5use crate::signal::Signal;
6
7/// Why a shutdown was initiated.
8///
9/// Carried by [`ShutdownTrigger::trigger`](crate::ShutdownTrigger::trigger)
10/// and surfaced to observers via [`ShutdownToken::reason`](crate::ShutdownToken::reason)
11/// and to hooks via [`ShutdownHook::run`](crate::ShutdownHook::run).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum ShutdownReason {
15    /// Originated from a delivered OS signal.
16    Signal(Signal),
17    /// Programmatic shutdown request (admin endpoint, parent
18    /// supervisor, fatal-error branch, etc.).
19    Requested,
20    /// Graceful budget elapsed; remaining hooks were skipped.
21    Forced,
22    /// A timed operation exceeded its deadline.
23    Timeout,
24    /// Shutdown driven by an internal error condition.
25    Error,
26}
27
28impl ShutdownReason {
29    /// Short human-readable label.
30    #[must_use]
31    pub const fn description(self) -> &'static str {
32        match self {
33            Self::Signal(_) => "signal",
34            Self::Requested => "requested",
35            Self::Forced => "forced",
36            Self::Timeout => "timeout",
37            Self::Error => "error",
38        }
39    }
40
41    /// `true` if this reason was caused by a signal.
42    #[must_use]
43    pub const fn is_signal(self) -> bool {
44        matches!(self, Self::Signal(_))
45    }
46}
47
48impl fmt::Display for ShutdownReason {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Signal(s) => write!(f, "Signal({s})"),
52            Self::Requested => f.write_str("Requested"),
53            Self::Forced => f.write_str("Forced"),
54            Self::Timeout => f.write_str("Timeout"),
55            Self::Error => f.write_str("Error"),
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn description_labels() {
66        assert_eq!(ShutdownReason::Requested.description(), "requested");
67        assert_eq!(ShutdownReason::Forced.description(), "forced");
68        assert_eq!(ShutdownReason::Timeout.description(), "timeout");
69        assert_eq!(ShutdownReason::Error.description(), "error");
70        assert_eq!(
71            ShutdownReason::Signal(Signal::Terminate).description(),
72            "signal"
73        );
74    }
75
76    #[test]
77    fn is_signal_only_for_signal_variant() {
78        assert!(ShutdownReason::Signal(Signal::Interrupt).is_signal());
79        assert!(!ShutdownReason::Requested.is_signal());
80        assert!(!ShutdownReason::Forced.is_signal());
81    }
82
83    #[test]
84    fn display_renders_signal_label() {
85        let s = format!("{}", ShutdownReason::Signal(Signal::Terminate));
86        assert!(s.starts_with("Signal("));
87    }
88}