1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/// Desired handling of the current child process when it exits.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ExitIntent {
/// Apply the configured restart policy to an ordinary exit.
#[default]
FollowPolicy,
/// Keep the process stopped, but permit another signal attempt because no
/// successful delivery is currently in flight.
StopRetryable,
/// Keep the process stopped and reuse the successfully delivered request.
StopInFlight,
/// Restart after exit, but permit another signal attempt because no
/// successful delivery is currently in flight.
RestartRetryable,
/// Restart after exit and reuse the successfully delivered request.
RestartInFlight,
}
impl ExitIntent {
/// Records explicit stop intent before attempting signal delivery.
pub fn request_stop(self) -> Self {
Self::StopRetryable
}
/// Records that a stop signal was delivered successfully.
pub fn stop_delivered(self) -> Self {
if self.is_stop() {
Self::StopInFlight
} else {
self
}
}
/// Records failed stop delivery, permitting a later retry while preserving
/// the user's instruction not to restart when the child exits.
pub fn stop_delivery_failed(self) -> Self {
if self.is_stop() {
Self::StopRetryable
} else {
self
}
}
/// Replaces any prior exit intent with an explicit restart request.
pub fn request_restart(self) -> Self {
Self::RestartRetryable
}
/// Records that a restart signal was delivered successfully.
pub fn restart_delivered(self) -> Self {
if self.is_restart() {
Self::RestartInFlight
} else {
self
}
}
/// Records failed restart delivery, permitting a later retry while
/// preserving the user's instruction to restart after any eventual exit.
pub fn restart_delivery_failed(self) -> Self {
if self.is_restart() {
Self::RestartRetryable
} else {
self
}
}
/// Whether another stop input should attempt signal delivery.
pub fn accepts_stop_request(self) -> bool {
self != Self::StopInFlight
}
/// Whether another restart input should attempt signal delivery.
pub fn accepts_restart_request(self) -> bool {
self != Self::RestartInFlight
}
/// Whether this intent represents an explicit request to remain stopped.
pub fn is_stop(self) -> bool {
matches!(self, Self::StopRetryable | Self::StopInFlight)
}
/// Whether this intent represents an explicit restart request.
pub fn is_restart(self) -> bool {
matches!(self, Self::RestartRetryable | Self::RestartInFlight)
}
/// Records that hard-kill escalation failed, preserving the requested exit
/// behavior while permitting another signal attempt.
pub fn force_stop_delivery_failed(self) -> Self {
match self {
Self::StopInFlight => Self::StopRetryable,
Self::RestartInFlight => Self::RestartRetryable,
_ => self,
}
}
/// Whether a graceful stop is awaiting hard-kill escalation.
pub fn awaits_force_stop(self) -> bool {
matches!(self, Self::StopInFlight | Self::RestartInFlight)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failed_delivery_preserves_stop_intent_and_allows_retry() {
let intent = ExitIntent::default().request_stop().stop_delivery_failed();
assert!(intent.is_stop());
assert!(intent.accepts_stop_request());
assert!(!intent.awaits_force_stop());
}
#[test]
fn successful_delivery_suppresses_duplicate_stop_requests() {
let intent = ExitIntent::default().request_stop().stop_delivered();
assert!(intent.is_stop());
assert!(!intent.accepts_stop_request());
assert!(intent.awaits_force_stop());
}
#[test]
fn restart_replaces_a_prior_stop_intent() {
let intent = ExitIntent::default()
.request_stop()
.stop_delivered()
.request_restart();
assert_eq!(intent, ExitIntent::RestartRetryable);
assert!(!intent.is_stop());
assert!(intent.is_restart());
}
/// A delivered restart waits for exit and permits timeout escalation.
#[test]
fn successful_restart_delivery_awaits_force_stop() {
let intent = ExitIntent::default().request_restart().restart_delivered();
assert_eq!(intent, ExitIntent::RestartInFlight);
assert!(!intent.accepts_restart_request());
assert!(intent.awaits_force_stop());
}
/// Failed escalation keeps the user's restart intent retryable.
#[test]
fn failed_restart_escalation_remains_retryable() {
let intent = ExitIntent::RestartInFlight.force_stop_delivery_failed();
assert_eq!(intent, ExitIntent::RestartRetryable);
assert!(intent.accepts_restart_request());
}
}