Skip to main content

nexus_memory_hooks/
signal.rs

1//! Signal handling for graceful shutdown
2//!
3//! This module provides cross-platform signal handling to ensure
4//! buffer flush before exit on SIGTERM/SIGINT.
5
6use futures::StreamExt;
7use std::sync::Arc;
8use tokio::sync::{broadcast, Mutex};
9
10use crate::error::{HookError, Result};
11
12/// Signal event types
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SignalEvent {
15    /// SIGINT (Ctrl+C)
16    Interrupt,
17
18    /// SIGTERM
19    Terminate,
20
21    /// SIGHUP (hangup)
22    Hangup,
23
24    /// User-defined signal 1
25    User1,
26
27    /// User-defined signal 2
28    User2,
29}
30
31/// Signal handler configuration
32pub struct SignalConfig {
33    /// Handle SIGINT
34    pub handle_interrupt: bool,
35
36    /// Handle SIGTERM
37    pub handle_terminate: bool,
38
39    /// Handle SIGHUP
40    pub handle_hangup: bool,
41}
42
43impl Default for SignalConfig {
44    fn default() -> Self {
45        Self {
46            handle_interrupt: true,
47            handle_terminate: true,
48            handle_hangup: false,
49        }
50    }
51}
52
53/// Signal handler for graceful shutdown
54pub struct SignalHandler {
55    /// Event sender
56    event_sender: broadcast::Sender<SignalEvent>,
57
58    /// Configuration
59    config: SignalConfig,
60
61    /// Whether handler is installed
62    installed: Arc<Mutex<bool>>,
63}
64
65impl SignalHandler {
66    /// Create a new signal handler
67    pub fn new() -> Self {
68        let (event_sender, _) = broadcast::channel(16);
69
70        Self {
71            event_sender,
72            config: SignalConfig::default(),
73            installed: Arc::new(Mutex::new(false)),
74        }
75    }
76
77    /// Create with custom configuration
78    pub fn with_config(config: SignalConfig) -> Self {
79        let (event_sender, _) = broadcast::channel(16);
80
81        Self {
82            event_sender,
83            config,
84            installed: Arc::new(Mutex::new(false)),
85        }
86    }
87
88    /// Subscribe to signal events
89    pub fn subscribe(&self) -> broadcast::Receiver<SignalEvent> {
90        self.event_sender.subscribe()
91    }
92
93    /// Install signal handlers
94    #[cfg(unix)]
95    pub async fn install(&self) -> Result<()> {
96        let mut installed = self.installed.lock().await;
97        if *installed {
98            return Ok(());
99        }
100
101        use signal_hook::consts::*;
102        use signal_hook_tokio::Signals;
103
104        let mut signals_to_handle = Vec::new();
105
106        if self.config.handle_interrupt {
107            signals_to_handle.push(SIGINT);
108        }
109        if self.config.handle_terminate {
110            signals_to_handle.push(SIGTERM);
111        }
112        if self.config.handle_hangup {
113            signals_to_handle.push(SIGHUP);
114        }
115
116        let mut signals = Signals::new(signals_to_handle).map_err(|e| {
117            HookError::SignalError(format!("Failed to create signal handler: {}", e))
118        })?;
119
120        let event_sender = self.event_sender.clone();
121        let _installed_flag = self.installed.clone();
122
123        tokio::spawn(async move {
124            while let Some(signal) = signals.next().await {
125                let event = match signal {
126                    SIGINT => SignalEvent::Interrupt,
127                    SIGTERM => SignalEvent::Terminate,
128                    SIGHUP => SignalEvent::Hangup,
129                    SIGUSR1 => SignalEvent::User1,
130                    SIGUSR2 => SignalEvent::User2,
131                    _ => continue,
132                };
133
134                let _ = event_sender.send(event);
135            }
136        });
137
138        *installed = true;
139        Ok(())
140    }
141
142    /// Install signal handlers (Windows)
143    #[cfg(windows)]
144    pub async fn install(&self) -> Result<()> {
145        let mut installed = self.installed.lock().await;
146        if *installed {
147            return Ok(());
148        }
149
150        use tokio::signal;
151
152        let event_sender = self.event_sender.clone();
153        let event_sender_ctrl = event_sender.clone();
154
155        // Handle Ctrl+C
156        if self.config.handle_interrupt {
157            tokio::spawn(async move {
158                match signal::ctrl_c().await {
159                    Ok(()) => {
160                        let _ = event_sender_ctrl.send(SignalEvent::Interrupt);
161                    }
162                    Err(e) => {
163                        tracing::error!("Ctrl+C handler error: {}", e);
164                    }
165                }
166            });
167        }
168
169        *installed = true;
170        Ok(())
171    }
172
173    /// Check if handler is installed
174    pub async fn is_installed(&self) -> bool {
175        *self.installed.lock().await
176    }
177
178    /// Send a signal event manually
179    pub fn send(&self, event: SignalEvent) -> Result<()> {
180        self.event_sender
181            .send(event)
182            .map_err(|e| HookError::SignalError(format!("Failed to send signal: {}", e)))?;
183        Ok(())
184    }
185}
186
187impl Default for SignalHandler {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193/// Run a callback on signal
194pub async fn on_signal<F>(handler: &SignalHandler, callback: F) -> Result<()>
195where
196    F: FnOnce() + Send + 'static,
197{
198    let mut receiver = handler.subscribe();
199
200    tokio::spawn(async move {
201        if let Ok(_signal) = receiver.recv().await {
202            callback();
203        }
204    });
205
206    Ok(())
207}
208
209/// Run an async callback on signal
210pub async fn on_signal_async<F, Fut>(handler: &SignalHandler, callback: F) -> Result<()>
211where
212    F: FnOnce() -> Fut + Send + 'static,
213    Fut: std::future::Future<Output = ()> + Send,
214{
215    let mut receiver = handler.subscribe();
216
217    tokio::spawn(async move {
218        if let Ok(_signal) = receiver.recv().await {
219            callback().await;
220        }
221    });
222
223    Ok(())
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[tokio::test]
231    async fn test_signal_handler_new() {
232        let handler = SignalHandler::new();
233        assert!(!handler.is_installed().await);
234    }
235
236    #[tokio::test]
237    async fn test_signal_handler_subscribe() {
238        let handler = SignalHandler::new();
239        let mut receiver = handler.subscribe();
240
241        // Send test event
242        handler.send(SignalEvent::Interrupt).unwrap();
243
244        let event = receiver.try_recv();
245        assert!(event.is_ok());
246        assert_eq!(event.unwrap(), SignalEvent::Interrupt);
247    }
248
249    #[tokio::test]
250    async fn test_signal_config_default() {
251        let config = SignalConfig::default();
252        assert!(config.handle_interrupt);
253        assert!(config.handle_terminate);
254        assert!(!config.handle_hangup);
255    }
256}