signal-hook 0.3.10

Unix signal handling
Documentation
#![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());
}