Skip to main content

node_headless/
shutdown.rs

1//! Platform shutdown-signal integration.
2
3use std::{
4    io,
5    sync::{
6        Arc,
7        atomic::{AtomicBool, Ordering},
8    },
9};
10
11#[cfg(unix)]
12use signal_hook::consts::signal::{SIGINT, SIGTERM};
13use wowlab_node::NodeCore;
14
15pub(crate) fn install(_core: &NodeCore, running: Arc<AtomicBool>) -> io::Result<()> {
16    #[cfg(unix)]
17    install_unix_signals(&[SIGINT, SIGTERM], running)?;
18
19    #[cfg(windows)]
20    {
21        _core.runtime_handle().spawn(async move {
22            if tokio::signal::ctrl_c().await.is_ok() {
23                tracing::info!("Received Ctrl-C; shutting down");
24                request_stop(&running);
25            }
26        });
27    }
28
29    #[cfg(not(any(unix, windows)))]
30    let _ = running;
31
32    Ok(())
33}
34
35#[cfg(unix)]
36fn install_unix_signals(signals: &[i32], running: Arc<AtomicBool>) -> io::Result<()> {
37    let mut signals = signal_hook::iterator::Signals::new(signals.iter().copied())?;
38
39    std::thread::Builder::new()
40        .name("node-shutdown".to_string())
41        .spawn(move || {
42            if signals.forever().next().is_some() {
43                request_stop(&running);
44            }
45        })?;
46
47    Ok(())
48}
49
50fn request_stop(running: &AtomicBool) {
51    running.store(false, Ordering::SeqCst);
52}
53
54#[cfg(test)]
55mod tests {
56    use googletest::prelude::*;
57
58    use super::*;
59
60    #[gtest]
61    fn shutdown_request_clears_running_flag() -> Result<()> {
62        let running = AtomicBool::new(true);
63
64        request_stop(&running);
65
66        verify_false!(running.load(Ordering::SeqCst))
67    }
68
69    #[cfg(unix)]
70    #[gtest]
71    fn registered_unix_signal_stops_foreground_driver() -> Result<()> {
72        use std::time::Duration;
73
74        let running = Arc::new(AtomicBool::new(true));
75
76        install_unix_signals(
77            &[signal_hook::consts::signal::SIGUSR1],
78            Arc::clone(&running),
79        )
80        .or_fail()?;
81
82        signal_hook::low_level::raise(signal_hook::consts::signal::SIGUSR1).or_fail()?;
83
84        for _ in 0..100 {
85            if !running.load(Ordering::SeqCst) {
86                break;
87            }
88
89            std::thread::sleep(Duration::from_millis(10));
90        }
91
92        verify_false!(running.load(Ordering::SeqCst))
93    }
94}