| 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"); |
| } |
| } |