rust_supervisor/exit_handler.rs
1//! Exit handler abstraction for testable process termination.
2//!
3//! The default implementation calls `std::process::exit(1)` as a last resort
4//! when orphaned tasks exceed the configured threshold. Tests can swap in a
5//! stub that records the exit request instead of terminating the process.
6
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9
10/// Abstraction for process exit, allowing tests to observe exit requests
11/// without actually terminating the process.
12pub trait ExitHandler: Send + Sync + std::fmt::Debug {
13 /// Terminates the process (or records the request in test mode).
14 fn exit(&self, code: i32);
15}
16
17/// Default exit handler that calls `std::process::exit(code)`.
18#[derive(Debug, Clone, Copy)]
19pub struct DefaultExitHandler;
20
21impl ExitHandler for DefaultExitHandler {
22 /// Terminates the current process with the provided code.
23 fn exit(&self, code: i32) {
24 std::process::exit(code);
25 }
26}
27
28/// Test-friendly exit handler that records a flag instead of terminating.
29#[derive(Debug, Clone)]
30pub struct TestExitHandler {
31 /// Set to `true` when `exit()` was called.
32 pub called: Arc<AtomicBool>,
33 /// Captured exit code.
34 pub exit_code: Arc<std::sync::Mutex<Option<i32>>>,
35}
36
37impl TestExitHandler {
38 /// Creates a new test exit handler with `called = false`.
39 pub fn new() -> Self {
40 Self {
41 called: Arc::new(AtomicBool::new(false)),
42 exit_code: Arc::new(std::sync::Mutex::new(None)),
43 }
44 }
45
46 /// Returns `true` when `exit()` was called since the last reset.
47 pub fn was_called(&self) -> bool {
48 self.called.load(Ordering::SeqCst)
49 }
50
51 /// Returns the exit code if `exit()` was called.
52 pub fn last_exit_code(&self) -> Option<i32> {
53 *self.exit_code.lock().unwrap_or_else(|e| e.into_inner())
54 }
55
56 /// Resets the recorded state.
57 pub fn reset(&self) {
58 self.called.store(false, Ordering::SeqCst);
59 *self.exit_code.lock().unwrap_or_else(|e| e.into_inner()) = None;
60 }
61}
62
63impl ExitHandler for TestExitHandler {
64 /// Records the requested exit code without terminating the process.
65 fn exit(&self, code: i32) {
66 self.called.store(true, Ordering::SeqCst);
67 *self.exit_code.lock().unwrap_or_else(|e| e.into_inner()) = Some(code);
68 // Do NOT call std::process::exit — let the test continue.
69 }
70}
71
72impl Default for TestExitHandler {
73 /// Creates a default test exit handler.
74 fn default() -> Self {
75 Self::new()
76 }
77}