Skip to main content

rs_teststand/execution/
termination_option.rs

1//! How a thread answers a request to terminate its execution.
2
3/// What a thread does when its execution is asked to terminate
4/// (`ThreadTerminationOptions`).
5///
6/// An execution cannot finish terminating while any of its threads refuses to
7/// stop, so this decides whether `Execution::terminate` completes promptly or
8/// waits.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[non_exhaustive]
11pub enum ThreadTerminationOption {
12    /// Stop with the execution. The default, and what an unattended host wants
13    /// almost everywhere.
14    Normal,
15    /// Refuse to stop unless the execution is told to override refusals.
16    ///
17    /// When every remaining thread chooses this, the engine posts
18    /// `UIMsg_NonTerminatableThreadsArePreventingTermination` and waits. On a
19    /// station with somebody watching, that is a question. On a headless host
20    /// it is a termination that never finishes unless the host handles that
21    /// message and overrides, so treat this as something to notice rather than
22    /// something to set casually.
23    Prompt,
24    /// Never stop with the execution; the thread runs to its own end first.
25    ///
26    /// For work that must not be cut in half, such as leaving hardware in a
27    /// safe state. The execution cannot end until the thread does, so anything
28    /// choosing this needs a bounded amount of work left to do.
29    Never,
30}
31
32impl ThreadTerminationOption {
33    /// Maps the engine's number onto an option.
34    ///
35    /// # Errors
36    /// The raw value, when it is one this build does not name.
37    pub const fn from_bits(bits: i32) -> Result<Self, i32> {
38        match bits {
39            0 => Ok(Self::Normal),
40            1 => Ok(Self::Prompt),
41            2 => Ok(Self::Never),
42            unknown => Err(unknown),
43        }
44    }
45
46    /// The engine's number for this option.
47    #[must_use]
48    pub const fn bits(self) -> i32 {
49        match self {
50            Self::Normal => 0,
51            Self::Prompt => 1,
52            Self::Never => 2,
53        }
54    }
55
56    /// Whether a thread with this option stops when its execution terminates.
57    ///
58    /// False for both [`Prompt`](Self::Prompt) and [`Never`](Self::Never), so a
59    /// host can tell in one call whether a thread is going to hold up a
60    /// termination.
61    #[must_use]
62    pub const fn stops_with_execution(self) -> bool {
63        matches!(self, Self::Normal)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::ThreadTerminationOption;
70
71    #[test]
72    fn every_documented_value_round_trips() {
73        for raw in 0..=2 {
74            assert_eq!(
75                ThreadTerminationOption::from_bits(raw).map(ThreadTerminationOption::bits),
76                Ok(raw),
77                "{raw} is documented but did not round-trip"
78            );
79        }
80    }
81
82    #[test]
83    fn an_unknown_value_is_returned_rather_than_guessed() {
84        assert_eq!(ThreadTerminationOption::from_bits(7), Err(7));
85    }
86
87    #[test]
88    fn only_normal_stops_with_its_execution() {
89        // The distinction a host branches on when a terminate does not finish.
90        assert!(ThreadTerminationOption::Normal.stops_with_execution());
91        assert!(!ThreadTerminationOption::Prompt.stops_with_execution());
92        assert!(!ThreadTerminationOption::Never.stops_with_execution());
93    }
94}