102 lines
3.6 KiB
Rust
102 lines
3.6 KiB
Rust
use nix::sys::signal::{
|
|
sigaction, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify,
|
|
Signal,
|
|
};
|
|
use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags};
|
|
use nix::time::ClockId;
|
|
use std::convert::TryFrom;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
const SIG: Signal = Signal::SIGALRM;
|
|
static ALARM_CALLED: AtomicBool = AtomicBool::new(false);
|
|
|
|
pub extern "C" fn handle_sigalarm(raw_signal: libc::c_int) {
|
|
let signal = Signal::try_from(raw_signal).unwrap();
|
|
if signal == SIG {
|
|
ALARM_CALLED.store(true, Ordering::Release);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn alarm_fires() {
|
|
// Avoid interfering with other signal using tests by taking a mutex shared
|
|
// among other tests in this crate.
|
|
let _m = crate::SIGNAL_MTX.lock();
|
|
const TIMER_PERIOD: Duration = Duration::from_millis(100);
|
|
|
|
//
|
|
// Setup
|
|
//
|
|
|
|
// Create a handler for the test signal, `SIG`. The handler is responsible
|
|
// for flipping `ALARM_CALLED`.
|
|
let handler = SigHandler::Handler(handle_sigalarm);
|
|
let signal_action =
|
|
SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
|
|
let old_handler = unsafe {
|
|
sigaction(SIG, &signal_action)
|
|
.expect("unable to set signal handler for alarm")
|
|
};
|
|
|
|
// Create the timer. We use the monotonic clock here, though any would do
|
|
// really. The timer is set to fire every 250 milliseconds with no delay for
|
|
// the initial firing.
|
|
let clockid = ClockId::CLOCK_MONOTONIC;
|
|
let sigevent = SigEvent::new(SigevNotify::SigevSignal {
|
|
signal: SIG,
|
|
si_value: 0,
|
|
});
|
|
let mut timer =
|
|
Timer::new(clockid, sigevent).expect("failed to create timer");
|
|
let expiration = Expiration::Interval(TIMER_PERIOD.into());
|
|
let flags = TimerSetTimeFlags::empty();
|
|
timer.set(expiration, flags).expect("could not set timer");
|
|
|
|
//
|
|
// Test
|
|
//
|
|
|
|
// Determine that there's still an expiration tracked by the
|
|
// timer. Depending on when this runs either an `Expiration::Interval` or
|
|
// `Expiration::IntervalDelayed` will be present. That is, if the timer has
|
|
// not fired yet we'll get our original `expiration`, else the one that
|
|
// represents a delay to the next expiration. We're only interested in the
|
|
// timer still being extant.
|
|
match timer.get() {
|
|
Ok(Some(exp)) => assert!(matches!(
|
|
exp,
|
|
Expiration::Interval(..) | Expiration::IntervalDelayed(..)
|
|
)),
|
|
_ => panic!("timer lost its expiration"),
|
|
}
|
|
|
|
// Wait for 2 firings of the alarm before checking that it has fired and
|
|
// been handled at least the once. If we wait for 3 seconds and the handler
|
|
// is never called something has gone sideways and the test fails.
|
|
let starttime = Instant::now();
|
|
loop {
|
|
thread::sleep(2 * TIMER_PERIOD);
|
|
if ALARM_CALLED.load(Ordering::Acquire) {
|
|
break;
|
|
}
|
|
if starttime.elapsed() > Duration::from_secs(3) {
|
|
panic!("Timeout waiting for SIGALRM");
|
|
}
|
|
}
|
|
|
|
// Cleanup:
|
|
// 1) deregister the OS's timer.
|
|
// 2) Wait for a full timer period, since POSIX does not require that
|
|
// disabling the timer will clear pending signals, and on NetBSD at least
|
|
// it does not.
|
|
// 2) Replace the old signal handler now that we've completed the test. If
|
|
// the test fails this process panics, so the fact we might not get here
|
|
// is okay.
|
|
drop(timer);
|
|
thread::sleep(TIMER_PERIOD);
|
|
unsafe {
|
|
sigaction(SIG, &old_handler).expect("unable to reset signal handler");
|
|
}
|
|
}
|