Skip to main content

restart_manager/
shutdown.rs

1//! Options and outcomes for shutdown and restart operations.
2
3use crate::{Error, Result};
4
5/// Options accepted by [`crate::RestartSession::shutdown_with_options`].
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7pub struct ShutdownOptions {
8    force_if_hung: bool,
9    require_restart_registration: bool,
10}
11
12impl ShutdownOptions {
13    /// Creates the graceful default.
14    #[must_use]
15    pub const fn new() -> Self {
16        Self {
17            force_if_hung: false,
18            require_restart_registration: false,
19        }
20    }
21
22    /// Chooses whether unresponsive applications may be force-terminated.
23    ///
24    /// Enabling this can cause data loss in affected applications.
25    #[must_use]
26    pub const fn with_force_if_hung(mut self, enabled: bool) -> Self {
27        self.force_if_hung = enabled;
28        self
29    }
30
31    /// Requires every affected application to have registered for restart.
32    #[must_use]
33    pub const fn with_require_restart_registration(mut self, enabled: bool) -> Self {
34        self.require_restart_registration = enabled;
35        self
36    }
37
38    /// Returns whether forced termination is enabled.
39    #[must_use]
40    pub const fn force_if_hung(self) -> bool {
41        self.force_if_hung
42    }
43
44    /// Returns whether every affected application must be restartable.
45    #[must_use]
46    pub const fn require_restart_registration(self) -> bool {
47        self.require_restart_registration
48    }
49
50    pub(crate) const fn native_flags(self) -> u32 {
51        let mut flags = 0;
52        if self.force_if_hung {
53            flags |= 0x01;
54        }
55        if self.require_restart_registration {
56            flags |= 0x10;
57        }
58        flags
59    }
60}
61
62/// The retained result of one native shutdown or restart attempt.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum OperationOutcome {
65    /// The native operation completed successfully.
66    Succeeded,
67    /// The native operation returned an error.
68    Failed(Error),
69}
70
71impl OperationOutcome {
72    pub(crate) fn from_result(result: Result<()>) -> Self {
73        match result {
74            Ok(()) => Self::Succeeded,
75            Err(error) => Self::Failed(error),
76        }
77    }
78
79    /// Returns whether the operation succeeded.
80    #[must_use]
81    pub const fn is_success(&self) -> bool {
82        matches!(self, Self::Succeeded)
83    }
84
85    /// Returns the retained error, if any.
86    #[must_use]
87    pub const fn error(&self) -> Option<&Error> {
88        match self {
89            Self::Succeeded => None,
90            Self::Failed(error) => Some(error),
91        }
92    }
93
94    /// Converts the retained value back into the crate result type.
95    pub fn into_result(self) -> Result<()> {
96        match self {
97            Self::Succeeded => Ok(()),
98            Self::Failed(error) => Err(error),
99        }
100    }
101}
102
103/// Results from the shutdown attempt and the optional restart attempt.
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct RecoveryOutcome {
106    pub(crate) shutdown: OperationOutcome,
107    pub(crate) restart: Option<OperationOutcome>,
108}
109
110impl RecoveryOutcome {
111    /// Returns the shutdown result.
112    #[must_use]
113    pub const fn shutdown_outcome(&self) -> &OperationOutcome {
114        &self.shutdown
115    }
116
117    /// Returns the restart result, or `None` after `leave_stopped`.
118    #[must_use]
119    pub const fn restart_outcome(&self) -> Option<&OperationOutcome> {
120        self.restart.as_ref()
121    }
122}
123
124/// Progress reported by a blocking shutdown or restart operation.
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
126pub struct Progress {
127    percent_complete: u8,
128}
129
130impl Progress {
131    pub(crate) fn try_from_native(percent_complete: u32) -> Option<Self> {
132        u8::try_from(percent_complete)
133            .ok()
134            .filter(|percent| *percent <= 100)
135            .map(|percent_complete| Self { percent_complete })
136    }
137
138    /// Returns a validated value in `0..=100`.
139    #[must_use]
140    pub const fn percent_complete(self) -> u8 {
141        self.percent_complete
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn progress_is_validated_and_ordered() {
151        assert_eq!(
152            Progress::try_from_native(42).unwrap().percent_complete(),
153            42
154        );
155        assert!(Progress::try_from_native(101).is_none());
156        assert!(Progress::try_from_native(10) < Progress::try_from_native(20));
157    }
158
159    #[test]
160    fn shutdown_flags_are_composable() {
161        let options = ShutdownOptions::new()
162            .with_force_if_hung(true)
163            .with_require_restart_registration(true);
164        assert_eq!(options.native_flags(), 0x11);
165        assert!(options.force_if_hung());
166        assert!(options.require_restart_registration());
167    }
168
169    #[test]
170    fn operation_outcomes_preserve_success_and_failure() {
171        let success = OperationOutcome::from_result(Ok(()));
172        assert!(success.is_success());
173        assert!(success.error().is_none());
174        success.into_result().unwrap();
175
176        let error = Error::new(
177            crate::ErrorKind::Cancelled,
178            Some(1223),
179            "cancelled for test",
180        );
181        let failure = OperationOutcome::from_result(Err(error.clone()));
182        assert!(!failure.is_success());
183        assert_eq!(failure.error(), Some(&error));
184        assert_eq!(failure.into_result().unwrap_err(), error);
185    }
186}