graceful shutdown - 优雅的关闭
在日常开过程中,不仅仅需要关注程序的启动和运行中的逻辑,能够更好的处理程序的关闭有时也非常的重要(如:释放资源、日志记录、发送通知等),有时由于非常规的程序关闭,如果不能及时的获知,将会产生意想不到的重大影响。
内容参考文档:Signal handling
最常规的优雅关闭就是信号的捕捉(signal handling)
三个常用箱子(库)
-
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..."); }
-
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(()) }
-
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(()) }