Skip to main content

start_command/
signal_handler.rs

1//! Signal handling for graceful cleanup on process interruption
2//!
3//! This module provides signal handlers that update execution status when the process
4//! is interrupted by signals like SIGINT (Ctrl+C), SIGTERM (kill), or SIGHUP (terminal close).
5
6use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
7use std::sync::Mutex;
8
9use crate::execution_store::{ExecutionRecord, ExecutionStore};
10
11// Global state for signal handling cleanup
12// These are used to update execution status when the process is interrupted
13static SIGNAL_RECEIVED: AtomicBool = AtomicBool::new(false);
14static SIGNAL_EXIT_CODE: AtomicI32 = AtomicI32::new(0);
15static CURRENT_EXECUTION: Mutex<Option<(ExecutionRecord, ExecutionStore)>> = Mutex::new(None);
16
17/// Check if a signal has been received
18#[allow(dead_code)]
19pub fn was_signal_received() -> bool {
20    SIGNAL_RECEIVED.load(Ordering::SeqCst)
21}
22
23/// Get the exit code from the received signal
24#[allow(dead_code)]
25pub fn get_signal_exit_code() -> i32 {
26    SIGNAL_EXIT_CODE.load(Ordering::SeqCst)
27}
28
29/// Set up signal handlers for graceful cleanup on interruption
30#[cfg(unix)]
31pub fn setup_signal_handlers() {
32    use std::sync::Once;
33    static INIT: Once = Once::new();
34
35    INIT.call_once(|| {
36        unsafe {
37            // SIGINT (Ctrl+C) - exit code 130 (128 + 2)
38            libc::signal(libc::SIGINT, signal_handler as *const () as usize);
39            // SIGTERM (kill command) - exit code 143 (128 + 15)
40            libc::signal(libc::SIGTERM, signal_handler as *const () as usize);
41            // SIGHUP (terminal closed) - exit code 129 (128 + 1)
42            libc::signal(libc::SIGHUP, signal_handler as *const () as usize);
43        }
44    });
45}
46
47#[cfg(not(unix))]
48pub fn setup_signal_handlers() {
49    // Signal handling not supported on non-Unix platforms
50}
51
52/// Signal handler function
53#[cfg(unix)]
54extern "C" fn signal_handler(sig: i32) {
55    // Calculate exit code based on signal (128 + signal number)
56    let exit_code = 128 + sig;
57    SIGNAL_EXIT_CODE.store(exit_code, Ordering::SeqCst);
58    SIGNAL_RECEIVED.store(true, Ordering::SeqCst);
59
60    // Try to clean up the current execution record
61    cleanup_execution_on_signal(sig, exit_code);
62
63    // Exit with the appropriate code
64    std::process::exit(exit_code);
65}
66
67/// Clean up execution record when a signal is received
68#[cfg(unix)]
69fn cleanup_execution_on_signal(signal: i32, exit_code: i32) {
70    if let Ok(mut guard) = CURRENT_EXECUTION.lock() {
71        if let Some((ref mut record, ref store)) = *guard {
72            // Mark as completed with signal exit code
73            record.complete(exit_code);
74            if let Err(e) = store.save(record) {
75                // Log error to stderr (can't easily check config here)
76                eprintln!(
77                    "\n[Tracking] Warning: Could not save execution record on signal {}: {}",
78                    signal, e
79                );
80            }
81            // Clear the record to prevent double cleanup
82            *guard = None;
83        }
84    }
85}
86
87/// Set the current execution record for signal cleanup
88pub fn set_current_execution(record: ExecutionRecord, store: ExecutionStore) {
89    if let Ok(mut guard) = CURRENT_EXECUTION.lock() {
90        *guard = Some((record, store));
91    }
92}
93
94/// Clear the current execution record (call after normal completion)
95pub fn clear_current_execution() {
96    if let Ok(mut guard) = CURRENT_EXECUTION.lock() {
97        *guard = None;
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_signal_received_initially_false() {
107        // Note: This test might be flaky if run after other tests that set the flag
108        // In a fresh process, SIGNAL_RECEIVED should be false
109        // We can't reliably test this after setup_signal_handlers is called
110    }
111
112    #[test]
113    fn test_set_and_clear_current_execution() {
114        // Just verify the functions don't panic
115        clear_current_execution();
116    }
117}