leo_lang/cli/commands/devnet/
shutdown.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crossbeam_channel::Sender;
18use std::{io, thread};
19
20pub fn install_shutdown_listener(tx: Sender<()>) -> io::Result<thread::JoinHandle<()>> {
21    #[cfg(unix)]
22    {
23        unix::install(tx)
24    }
25
26    #[cfg(windows)]
27    {
28        windows::install(tx)
29    }
30}
31
32#[cfg(unix)]
33mod unix {
34    use super::*;
35    use signal_hook::{consts::signal::*, iterator::Signals};
36
37    pub fn install(tx: Sender<()>) -> io::Result<thread::JoinHandle<()>> {
38        let mut signals = Signals::new([SIGINT, SIGTERM, SIGQUIT, SIGHUP])?;
39
40        Ok(thread::spawn(move || {
41            for _sig in signals.forever() {
42                let _ = tx.try_send(());
43            }
44        }))
45    }
46}
47
48#[cfg(windows)]
49mod windows {
50    use super::*;
51    use once_cell::sync::OnceCell;
52    use windows_sys::Win32::System::Console::{
53        CTRL_BREAK_EVENT,
54        CTRL_C_EVENT,
55        CTRL_CLOSE_EVENT,
56        CTRL_LOGOFF_EVENT,
57        CTRL_SHUTDOWN_EVENT,
58        SetConsoleCtrlHandler,
59    };
60
61    // Lock-free global slot for the sender so the console handler can notify.
62    static TX_SLOT: OnceCell<Sender<()>> = OnceCell::new();
63
64    pub fn install(tx: Sender<()>) -> io::Result<thread::JoinHandle<()>> {
65        // Ctrl+C / Ctrl+Break via ctrlc
66        ctrlc::set_handler({
67            let tx = tx.clone();
68            move || {
69                let _ = tx.try_send(());
70            }
71        })
72        .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
73
74        // Console window close, user logoff, system shutdown
75        enable_console_close_handler(tx)?;
76
77        // Keep a parked thread so caller can Join if desired; handlers are global.
78        Ok(std::thread::spawn(|| {
79            loop {
80                std::thread::park();
81            }
82        }))
83    }
84
85    fn enable_console_close_handler(tx: Sender<()>) -> io::Result<()> {
86        // Store the sender once, lock-free.
87        let _ = TX_SLOT.set(tx);
88
89        // Minimal, non-blocking handler: just try to send and return quickly.
90        #[allow(unsafe_code)]
91        unsafe extern "system" fn handler(ctrl: u32) -> i32 {
92            match ctrl {
93                CTRL_C_EVENT | CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT | CTRL_LOGOFF_EVENT | CTRL_SHUTDOWN_EVENT => {
94                    if let Some(tx) = TX_SLOT.get() {
95                        let _ = tx.try_send(());
96                    }
97                    1 // TRUE: handled
98                }
99                _ => 0, // FALSE: not handled
100            }
101        }
102
103        #[allow(unsafe_code)]
104        unsafe {
105            if SetConsoleCtrlHandler(Some(handler), 1) == 0 {
106                return Err(io::Error::new(io::ErrorKind::Other, "SetConsoleCtrlHandler failed"));
107            }
108        }
109        Ok(())
110    }
111}