graceful shutdown - 优雅的关闭


graceful shutdown - 优雅的关闭

在日常开过程中,不仅仅需要关注程序的启动和运行中的逻辑,能够更好的处理程序的关闭有时也非常的重要(如:释放资源、日志记录、发送通知等),有时由于非常规的程序关闭,如果不能及时的获知,将会产生意想不到的重大影响。

内容参考文档:Signal handling

最常规的优雅关闭就是信号的捕捉(signal handling)

三个常用箱子(库)

  • ctrlc

    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Arc;
      
    fn main() {
        let running = Arc::new(AtomicBool::new(true));
        let r = running.clone();
        ctrlc::set_handler(move || {
            r.store(false, Ordering::SeqCst);
        }).expect("Error setting Ctrl-C handler");
        println!("Waiting for Ctrl-C...");
        while running.load(Ordering::SeqCst) {}
        println!("Got it! Exiting...");
    }
    
  • signal-hook

    use std::io::Error;
    use std::sync::Arc;
    use std::sync::atomic::AtomicBool;
      
    use signal_hook::consts::signal::*;
    use signal_hook::consts::TERM_SIGNALS;
    use signal_hook::flag;
    // A friend of the Signals iterator, but can be customized by what we want yielded about each
    // signal.
    use signal_hook::iterator::SignalsInfo;
    use signal_hook::iterator::exfiltrator::WithOrigin;
    use signal_hook::low_level;
      
    fn main() -> Result<(), Error> {
        // Make sure double CTRL+C and similar kills
        let term_now = Arc::new(AtomicBool::new(false));
        for sig in TERM_SIGNALS {
            // When terminated by a second term signal, exit with exit code 1.
            // This will do nothing the first time (because term_now is false).
            flag::register_conditional_shutdown(*sig, 1, Arc::clone(&term_now))?;
            // But this will "arm" the above for the second time, by setting it to true.
            // The order of registering these is important, if you put this one first, it will
            // first arm and then terminate ‒ all in the first round.
            flag::register(*sig, Arc::clone(&term_now))?;
        }
      
        // Subscribe to all these signals with information about where they come from. We use the
        // extra info only for logging in this example (it is not available on all the OSes or at
        // all the occasions anyway, it may return `Unknown`).
        let mut sigs = vec![
            // Some terminal handling
            SIGTSTP, SIGCONT, SIGWINCH,
            // Reload of configuration for daemons ‒ um, is this example for a TUI app or a daemon
            // O:-)? You choose...
            SIGHUP,
            // Application-specific action, to print some statistics.
            SIGUSR1,
        ];
        sigs.extend(TERM_SIGNALS);
        let mut signals = SignalsInfo::<WithOrigin>::new(&sigs)?;
      
        // This is the actual application that'll start in its own thread. We'll control it from
        // this thread based on the signals, but it keeps running.
        // This is called after all the signals got registered, to avoid the short race condition
        // in the first registration of each signal in multi-threaded programs.
        let app = App::run_background();
      
        // Consume all the incoming signals. This happens in "normal" Rust thread, not in the
        // signal handlers. This means that we are allowed to do whatever we like in here, without
        // restrictions, but it also means the kernel believes the signal already got delivered, we
        // handle them in delayed manner. This is in contrast with eg the above
        // `register_conditional_shutdown` where the shutdown happens *inside* the handler.
        let mut has_terminal = true;
        for info in &mut signals {
            // Will print info about signal + where it comes from.
            eprintln!("Received a signal {:?}", info);
            match info.signal {
                SIGTSTP => {
                    // Restore the terminal to non-TUI mode
                    if has_terminal {
                        app.restore_term();
                        has_terminal = false;
                        // And actually stop ourselves, by a little trick.
                        low_level::raise(SIGSTOP)?;
                    }
                }
                SIGCONT => {
                    if !has_terminal {
                        app.claim_term();
                        has_terminal = true;
                    }
                }
                SIGWINCH => app.resize_term(),
                SIGHUP => app.reload_config(),
                SIGUSR1 => app.print_stats(),
                term_sig => { // These are all the ones left
                    eprintln!("Terminating");
                    assert!(TERM_SIGNALS.contains(&term_sig));
                    break;
                }
            }
        }
      
        // If during this another termination signal comes, the trick at the top would kick in and
        // terminate early. But if it doesn't, the application shuts down gracefully.
        app.wait_for_stop();
      
        Ok(())
    }
    
  • crossbeam-channel

    use std::time::Duration;
    use crossbeam_channel::{bounded, tick, Receiver, select};
    use anyhow::Result;
      
    fn ctrl_channel() -> Result<Receiver<()>, ctrlc::Error> {
        let (sender, receiver) = bounded(100);
        ctrlc::set_handler(move || {
            let _ = sender.send(());
        })?;
      
        Ok(receiver)
    }
      
    fn main() -> Result<()> {
        let ctrl_c_events = ctrl_channel()?;
        let ticks = tick(Duration::from_secs(1));
      
        loop {
            select! {
                recv(ticks) -> _ => {
                    println!("working!");
                }
                recv(ctrl_c_events) -> _ => {
                    println!();
                    println!("Goodbye!");
                    break;
                }
            }
        }
      
        Ok(())
    }
    

其他

  • tokio的关闭处理方式

    extern crate libc;
    extern crate signal_hook;
    extern crate tokio;
      
    use std::io::Error;
      
    use signal_hook::iterator::Signals;
    use tokio::prelude::*;
      
    fn main() -> Result<(), Error> {
        let wait_signal = Signals::new(&[signal_hook::SIGUSR1])?
            .into_async()?
            .into_future()
            .map(|sig| assert_eq!(sig.0.unwrap(), signal_hook::SIGUSR1))
            .map_err(|e| panic!("{}", e.0));
        unsafe { libc::kill(libc::getpid(), signal_hook::SIGUSR1) };
        tokio::run(wait_signal);
        Ok(())
    }