node_headless/
shutdown.rs1use 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}