autocore_std/fb/
shutdown.rs1use crate::command_client::CommandClient;
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57enum PendingOp {
58 Initiate,
59 Cancel,
60}
61
62#[derive(Debug, Clone)]
63pub struct Shutdown {
64 pub busy: bool,
66 pub done: bool,
68 pub error: bool,
70 pub error_message: String,
72
73 pending_tid: Option<u32>,
74 pending_op: Option<PendingOp>,
75}
76
77impl Shutdown {
78 pub fn new() -> Self {
80 Self {
81 busy: false,
82 done: false,
83 error: false,
84 error_message: String::new(),
85 pending_tid: None,
86 pending_op: None,
87 }
88 }
89
90 pub fn initiate(&mut self, client: &mut CommandClient) {
95 if self.busy {
96 return;
97 }
98 let tid = client.send("system.full_shutdown", serde_json::json!({}));
99 self.pending_tid = Some(tid);
100 self.pending_op = Some(PendingOp::Initiate);
101 self.busy = true;
102 self.done = false;
103 self.error = false;
104 self.error_message.clear();
105 }
106
107 pub fn cancel(&mut self, client: &mut CommandClient) {
111 if self.busy {
112 return;
113 }
114 let tid = client.send("system.cancel_full_shutdown", serde_json::json!({}));
115 self.pending_tid = Some(tid);
116 self.pending_op = Some(PendingOp::Cancel);
117 self.busy = true;
118 self.done = false;
119 self.error = false;
120 self.error_message.clear();
121 }
122
123 pub fn call(&mut self, client: &mut CommandClient) {
128 if self.done || self.error {
130 self.done = false;
131 self.error = false;
132 self.error_message.clear();
133 }
134
135 let tid = match self.pending_tid {
136 Some(tid) => tid,
137 None => return,
138 };
139
140 if let Some(response) = client.take_response(tid) {
141 self.busy = false;
142 self.pending_tid = None;
143 self.pending_op = None;
144
145 if response.success {
146 self.done = true;
147 } else {
148 self.error = true;
149 self.error_message = response.error_message;
150 }
151 }
152 }
153
154 pub fn is_initiating(&self) -> bool {
156 self.pending_op == Some(PendingOp::Initiate)
157 }
158
159 pub fn is_cancelling(&self) -> bool {
161 self.pending_op == Some(PendingOp::Cancel)
162 }
163}
164
165impl Default for Shutdown {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use mechutil::ipc::CommandMessage;
175 use serde_json::json;
176 use tokio::sync::mpsc;
177
178 fn make_client() -> (CommandClient, mpsc::UnboundedSender<CommandMessage>) {
179 let (write_tx, _write_rx) = mpsc::unbounded_channel();
180 let (response_tx, response_rx) = mpsc::unbounded_channel();
181 (CommandClient::new(write_tx, response_rx), response_tx)
182 }
183
184 #[test]
185 fn test_initiate_sends_command() {
186 let (mut client, _response_tx) = make_client();
187 let mut shutdown = Shutdown::new();
188
189 assert!(!shutdown.busy);
190 shutdown.initiate(&mut client);
191
192 assert!(shutdown.busy);
193 assert!(shutdown.is_initiating());
194 assert!(!shutdown.is_cancelling());
195 assert_eq!(client.pending_count(), 1);
196 }
197
198 #[test]
199 fn test_cancel_sends_command() {
200 let (mut client, _response_tx) = make_client();
201 let mut shutdown = Shutdown::new();
202
203 shutdown.cancel(&mut client);
204
205 assert!(shutdown.busy);
206 assert!(shutdown.is_cancelling());
207 assert!(!shutdown.is_initiating());
208 assert_eq!(client.pending_count(), 1);
209 }
210
211 #[test]
212 fn test_ignores_while_busy() {
213 let (mut client, _response_tx) = make_client();
214 let mut shutdown = Shutdown::new();
215
216 shutdown.initiate(&mut client);
217 assert_eq!(client.pending_count(), 1);
218
219 shutdown.initiate(&mut client);
221 assert_eq!(client.pending_count(), 1);
222
223 shutdown.cancel(&mut client);
225 assert_eq!(client.pending_count(), 1);
226 }
227
228 #[test]
229 fn test_success_response() {
230 let (mut client, response_tx) = make_client();
231 let mut shutdown = Shutdown::new();
232
233 shutdown.initiate(&mut client);
234 let tid = shutdown.pending_tid.unwrap();
235
236 response_tx.send(CommandMessage::response(tid, json!({"status": "shutdown_scheduled"}))).unwrap();
238 client.poll();
239
240 shutdown.call(&mut client);
241
242 assert!(!shutdown.busy);
243 assert!(shutdown.done);
244 assert!(!shutdown.error);
245 }
246
247 #[test]
248 fn test_error_response() {
249 let (mut client, response_tx) = make_client();
250 let mut shutdown = Shutdown::new();
251
252 shutdown.initiate(&mut client);
253 let tid = shutdown.pending_tid.unwrap();
254
255 let mut resp = CommandMessage::response(tid, json!(null));
257 resp.success = false;
258 resp.error_message = "Shutdown already scheduled".to_string();
259 response_tx.send(resp).unwrap();
260 client.poll();
261
262 shutdown.call(&mut client);
263
264 assert!(!shutdown.busy);
265 assert!(!shutdown.done);
266 assert!(shutdown.error);
267 assert_eq!(shutdown.error_message, "Shutdown already scheduled");
268 }
269
270 #[test]
271 fn test_done_clears_after_one_cycle() {
272 let (mut client, response_tx) = make_client();
273 let mut shutdown = Shutdown::new();
274
275 shutdown.initiate(&mut client);
276 let tid = shutdown.pending_tid.unwrap();
277
278 response_tx.send(CommandMessage::response(tid, json!({"status": "shutdown_scheduled"}))).unwrap();
279 client.poll();
280
281 shutdown.call(&mut client);
283 assert!(shutdown.done);
284
285 shutdown.call(&mut client);
287 assert!(!shutdown.done);
288 }
289
290 #[test]
291 fn test_cancel_after_initiate_completes() {
292 let (mut client, response_tx) = make_client();
293 let mut shutdown = Shutdown::new();
294
295 shutdown.initiate(&mut client);
297 let tid1 = shutdown.pending_tid.unwrap();
298
299 response_tx.send(CommandMessage::response(tid1, json!({"status": "shutdown_scheduled"}))).unwrap();
300 client.poll();
301 shutdown.call(&mut client);
302 assert!(shutdown.done);
303
304 shutdown.call(&mut client);
306
307 shutdown.cancel(&mut client);
309 assert!(shutdown.busy);
310 assert!(shutdown.is_cancelling());
311
312 let tid2 = shutdown.pending_tid.unwrap();
313 response_tx.send(CommandMessage::response(tid2, json!({"status": "shutdown_cancelled"}))).unwrap();
314 client.poll();
315 shutdown.call(&mut client);
316
317 assert!(shutdown.done);
318 assert!(!shutdown.busy);
319 }
320}