//! Tests for the shutdown. //! //! The tests work like this: //! //! * The register an alarm, to fail if anything takes too long (which is very much possible here). //! * A fork is done, with the child registering a signal with a NOP and cleanup operation (one or //! the other). //! * The child puts some kind of infinite loop or sleep inside itself, so it never actually //! terminates on the first, but would terminate after the signal. #![cfg(not(windows))] // Forks don't work on Windows, but windows has the same implementation. use std::io::Error; use std::ptr; use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::thread; use std::time::Duration; use signal_hook::consts::signal::*; use signal_hook::flag; use signal_hook::low_level; fn do_test(child: C) { unsafe { libc::alarm(10); // Time out the test after 10 seconds and get it killed. match libc::fork() { -1 => panic!("Fork failed: {}", Error::last_os_error()), 0 => { child(); loop { thread::sleep(Duration::from_secs(1)); } } pid => { // Give the child some time to register signals and stuff // We could actually signal that the child is ready by it eg. closing STDOUT, but // this is just a test so we don't really bother. thread::sleep(Duration::from_millis(250)); libc::kill(pid, libc::SIGTERM); // Wait a small bit to make sure the signal got delivered. thread::sleep(Duration::from_millis(50)); // The child is still running, because the first signal got "handled" by being // ignored. let terminated = libc::waitpid(pid, ptr::null_mut(), libc::WNOHANG); assert_eq!(0, terminated, "Process {} terminated prematurely", pid); // But it terminates on the second attempt (we do block on wait here). libc::kill(pid, libc::SIGTERM); let terminated = libc::waitpid(pid, ptr::null_mut(), 0); assert_eq!(pid, terminated); } } } } /// Use automatic cleanup inside the signal handler to get rid of old signals, the aggressive way. #[test] fn cleanup_inside_signal() { fn hook() { // Make sure we have some signal handler, not the default. unsafe { low_level::register(SIGTERM, || ()).unwrap() }; let shutdown_cond = Arc::new(AtomicBool::new(false)); // „disarmed“ shutdown flag::register_conditional_shutdown(SIGTERM, 0, Arc::clone(&shutdown_cond)).unwrap(); // But arm at the first SIGTERM flag::register(SIGTERM, shutdown_cond).unwrap(); } do_test(hook); } /// Manually remove the signal handler just after receiving the signal but before going into an /// infinite loop. #[test] fn cleanup_after_signal() { fn hook() { let mut signals = signal_hook::iterator::Signals::new(&[libc::SIGTERM]).unwrap(); assert_eq!(Some(SIGTERM), signals.into_iter().next()); flag::register_conditional_shutdown(SIGTERM, 0, Arc::new(AtomicBool::new(true))).unwrap(); } do_test(hook); }