xref: /third_party/rust/crates/nix/test/test_timer.rs (revision 3da5c369)
1use nix::sys::signal::{
2    sigaction, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify,
3    Signal,
4};
5use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags};
6use nix::time::ClockId;
7use std::convert::TryFrom;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::thread;
10use std::time::{Duration, Instant};
11
12const SIG: Signal = Signal::SIGALRM;
13static ALARM_CALLED: AtomicBool = AtomicBool::new(false);
14
15pub extern "C" fn handle_sigalarm(raw_signal: libc::c_int) {
16    let signal = Signal::try_from(raw_signal).unwrap();
17    if signal == SIG {
18        ALARM_CALLED.store(true, Ordering::Release);
19    }
20}
21
22#[test]
23fn alarm_fires() {
24    // Avoid interfering with other signal using tests by taking a mutex shared
25    // among other tests in this crate.
26    let _m = crate::SIGNAL_MTX.lock();
27    const TIMER_PERIOD: Duration = Duration::from_millis(100);
28
29    //
30    // Setup
31    //
32
33    // Create a handler for the test signal, `SIG`. The handler is responsible
34    // for flipping `ALARM_CALLED`.
35    let handler = SigHandler::Handler(handle_sigalarm);
36    let signal_action =
37        SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
38    let old_handler = unsafe {
39        sigaction(SIG, &signal_action)
40            .expect("unable to set signal handler for alarm")
41    };
42
43    // Create the timer. We use the monotonic clock here, though any would do
44    // really. The timer is set to fire every 250 milliseconds with no delay for
45    // the initial firing.
46    let clockid = ClockId::CLOCK_MONOTONIC;
47    let sigevent = SigEvent::new(SigevNotify::SigevSignal {
48        signal: SIG,
49        si_value: 0,
50    });
51    let mut timer =
52        Timer::new(clockid, sigevent).expect("failed to create timer");
53    let expiration = Expiration::Interval(TIMER_PERIOD.into());
54    let flags = TimerSetTimeFlags::empty();
55    timer.set(expiration, flags).expect("could not set timer");
56
57    //
58    // Test
59    //
60
61    // Determine that there's still an expiration tracked by the
62    // timer. Depending on when this runs either an `Expiration::Interval` or
63    // `Expiration::IntervalDelayed` will be present. That is, if the timer has
64    // not fired yet we'll get our original `expiration`, else the one that
65    // represents a delay to the next expiration. We're only interested in the
66    // timer still being extant.
67    match timer.get() {
68        Ok(Some(exp)) => assert!(matches!(
69            exp,
70            Expiration::Interval(..) | Expiration::IntervalDelayed(..)
71        )),
72        _ => panic!("timer lost its expiration"),
73    }
74
75    // Wait for 2 firings of the alarm before checking that it has fired and
76    // been handled at least the once. If we wait for 3 seconds and the handler
77    // is never called something has gone sideways and the test fails.
78    let starttime = Instant::now();
79    loop {
80        thread::sleep(2 * TIMER_PERIOD);
81        if ALARM_CALLED.load(Ordering::Acquire) {
82            break;
83        }
84        if starttime.elapsed() > Duration::from_secs(3) {
85            panic!("Timeout waiting for SIGALRM");
86        }
87    }
88
89    // Cleanup:
90    // 1) deregister the OS's timer.
91    // 2) Wait for a full timer period, since POSIX does not require that
92    //    disabling the timer will clear pending signals, and on NetBSD at least
93    //    it does not.
94    // 2) Replace the old signal handler now that we've completed the test. If
95    //    the test fails this process panics, so the fact we might not get here
96    //    is okay.
97    drop(timer);
98    thread::sleep(TIMER_PERIOD);
99    unsafe {
100        sigaction(SIG, &old_handler).expect("unable to reset signal handler");
101    }
102}
103