| #![cfg(not(windows))] |
| |
| extern crate signal_hook; |
| |
| use std::collections::HashSet; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::mpsc::{self, RecvTimeoutError}; |
| use std::sync::Arc; |
| use std::thread::{self, JoinHandle}; |
| use std::time::Duration; |
| |
| use signal_hook::consts::{SIGUSR1, SIGUSR2}; |
| use signal_hook::iterator::{Handle, Signals}; |
| use signal_hook::low_level::raise; |
| |
| use serial_test::serial; |
| |
| fn send_sigusr1() { |
| raise(SIGUSR1).unwrap(); |
| } |
| |
| fn send_sigusr2() { |
| raise(SIGUSR2).unwrap(); |
| } |
| |
| fn setup_without_any_signals() -> (Signals, Handle) { |
| let signals = Signals::new(&[]).unwrap(); |
| let controller = signals.handle(); |
| (signals, controller) |
| } |
| |
| fn setup_for_sigusr2() -> (Signals, Handle) { |
| let signals = Signals::new(&[SIGUSR2]).unwrap(); |
| let controller = signals.handle(); |
| (signals, controller) |
| } |
| |
| macro_rules! assert_signals { |
| ($actual:expr, $($expected:expr),+ $(,)?) => { |
| let actual = $actual.collect::<HashSet<libc::c_int>>(); |
| let expected = vec!($($expected),+).into_iter().collect::<HashSet<libc::c_int>>(); |
| assert_eq!(actual, expected); |
| }; |
| } |
| |
| macro_rules! assert_no_signals { |
| ($signals:expr) => { |
| assert_eq!($signals.next(), None); |
| }; |
| } |
| |
| #[test] |
| #[serial] |
| fn forever_terminates_when_closed() { |
| let (mut signals, controller) = setup_for_sigusr2(); |
| |
| // Detect early terminations. |
| let stopped = Arc::new(AtomicBool::new(false)); |
| |
| let stopped_bg = Arc::clone(&stopped); |
| let thread = thread::spawn(move || { |
| // Eat all the signals there are (might come from a concurrent test, in theory). |
| // Would wait forever, but it should be terminated by the close below. |
| for _sig in &mut signals {} |
| |
| stopped_bg.store(true, Ordering::SeqCst); |
| }); |
| |
| // Wait a bit to see if the thread terminates by itself. |
| thread::sleep(Duration::from_millis(100)); |
| assert!(!stopped.load(Ordering::SeqCst)); |
| |
| controller.close(); |
| |
| thread.join().unwrap(); |
| } |
| |
| // A reproducer for #16: if we had the mio-support enabled (which is enabled also by the |
| // tokio-support feature), blocking no longer works. The .wait() would return immediately (an empty |
| // iterator, possibly), .forever() would do a busy loop. |
| // flag) |
| #[test] |
| #[serial] |
| fn signals_block_wait() { |
| let mut signals = Signals::new(&[SIGUSR2]).unwrap(); |
| let (s, r) = mpsc::channel(); |
| let finish = Arc::new(AtomicBool::new(false)); |
| let thread_id = thread::spawn({ |
| let finish = Arc::clone(&finish); |
| move || { |
| // Technically, it may spuriously return early. But it shouldn't be doing it too much, |
| // so we just try to wait multiple times ‒ if they *all* return right away, it is |
| // broken. |
| for _ in 0..10 { |
| for _ in signals.wait() { |
| if finish.load(Ordering::SeqCst) { |
| // Asked to terminate at the end of the thread. Do so (but without |
| // signalling the receipt). |
| return; |
| } else { |
| panic!("Someone really did send us SIGUSR2, which breaks the test"); |
| } |
| } |
| } |
| let _ = s.send(()); |
| } |
| }); |
| |
| // A RAII guard to make sure we shut down the thread even if the test fails. |
| struct ThreadGuard { |
| thread: Option<JoinHandle<()>>, |
| finish: Arc<AtomicBool>, |
| } |
| |
| impl ThreadGuard { |
| fn shutdown(&mut self) { |
| // Tell it to shut down |
| self.finish.store(true, Ordering::SeqCst); |
| // Wake it up |
| send_sigusr2(); |
| // Wait for it to actually terminate. |
| if let Some(thread) = self.thread.take() { |
| thread.join().unwrap(); // Propagate panics |
| } |
| } |
| } |
| |
| impl Drop for ThreadGuard { |
| fn drop(&mut self) { |
| self.shutdown(); // OK if done twice, won't have the thread any more. |
| } |
| } |
| |
| let mut bg_thread = ThreadGuard { |
| thread: Some(thread_id), |
| finish, |
| }; |
| |
| let err = r |
| .recv_timeout(Duration::from_millis(100)) |
| .expect_err("Wait didn't wait properly"); |
| assert_eq!(err, RecvTimeoutError::Timeout); |
| |
| bg_thread.shutdown(); |
| } |
| |
| #[test] |
| #[serial] |
| fn pending_doesnt_block() { |
| let (mut signals, _) = setup_for_sigusr2(); |
| |
| let mut recieved_signals = signals.pending(); |
| |
| assert_no_signals!(recieved_signals); |
| } |
| |
| #[test] |
| #[serial] |
| fn wait_returns_recieved_signals() { |
| let (mut signals, _) = setup_for_sigusr2(); |
| send_sigusr2(); |
| |
| let recieved_signals = signals.wait(); |
| |
| assert_signals!(recieved_signals, SIGUSR2); |
| } |
| |
| #[test] |
| #[serial] |
| fn forever_returns_recieved_signals() { |
| let (mut signals, _) = setup_for_sigusr2(); |
| send_sigusr2(); |
| |
| let signal = signals.forever().take(1); |
| |
| assert_signals!(signal, SIGUSR2); |
| } |
| |
| #[test] |
| #[serial] |
| fn wait_doesnt_block_when_closed() { |
| let (mut signals, controller) = setup_for_sigusr2(); |
| controller.close(); |
| |
| let mut recieved_signals = signals.wait(); |
| |
| assert_no_signals!(recieved_signals); |
| } |
| |
| #[test] |
| #[serial] |
| fn wait_unblocks_when_closed() { |
| let (mut signals, controller) = setup_without_any_signals(); |
| |
| let thread = thread::spawn(move || { |
| signals.wait(); |
| }); |
| |
| controller.close(); |
| |
| thread.join().unwrap(); |
| } |
| |
| #[test] |
| #[serial] |
| fn forever_doesnt_block_when_closed() { |
| let (mut signals, controller) = setup_for_sigusr2(); |
| controller.close(); |
| |
| let mut signal = signals.forever(); |
| |
| assert_no_signals!(signal); |
| } |
| |
| #[test] |
| #[serial] |
| fn add_signal_after_creation() { |
| let (mut signals, _) = setup_without_any_signals(); |
| signals.add_signal(SIGUSR1).unwrap(); |
| |
| send_sigusr1(); |
| |
| assert_signals!(signals.pending(), SIGUSR1); |
| } |
| |
| #[test] |
| #[serial] |
| fn delayed_signal_consumed() { |
| let (mut signals, _) = setup_for_sigusr2(); |
| signals.add_signal(SIGUSR1).unwrap(); |
| |
| send_sigusr1(); |
| let mut recieved_signals = signals.wait(); |
| send_sigusr2(); |
| |
| assert_signals!(recieved_signals, SIGUSR1, SIGUSR2); |
| |
| // The pipe still contains the byte from the second |
| // signal and so wait won't block but won't return |
| // a signal. |
| recieved_signals = signals.wait(); |
| assert_no_signals!(recieved_signals); |
| } |
| |
| #[test] |
| #[serial] |
| fn is_closed_initially_returns_false() { |
| let (_, controller) = setup_for_sigusr2(); |
| |
| assert!(!controller.is_closed()); |
| } |
| |
| #[test] |
| #[serial] |
| fn is_closed_returns_true_when_closed() { |
| let (_, controller) = setup_for_sigusr2(); |
| controller.close(); |
| |
| assert!(controller.is_closed()); |
| } |