mirror of
https://gitee.com/openharmony/third_party_rust_nix
synced 2024-12-03 13:20:46 +00:00
1622: Introduce `timer_*` support r=asomers a=blt
This commit adds support for the signal timer mechanism in POSIX, the mirror to timerfd on Linux. I wasn't _quite_ sure of how to fit into the project organization but hopefully this patch isn't too far off.
Resolves #1424
Signed-off-by: Brian L. Troutwine <brian@troutwine.us>
1623: Remove cc dependency on DragonFly r=asomers a=rtzoeller
f5ee22db48
removed the need for this dependency.
Co-authored-by: Brian L. Troutwine <brian@troutwine.us>
Co-authored-by: Ryan Zoeller <rtzoeller@rtzoeller.com>
This commit is contained in:
commit
ee0543f126
@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
(#[1619](https://github.com/nix-rust/nix/pull/1619))
|
||||
- Added the `TxTime` sockopt and control message.
|
||||
(#[1564](https://github.com/nix-rust/nix/pull/1564))
|
||||
- Added POSIX per-process timer support
|
||||
(#[1622](https://github.com/nix-rust/nix/pull/1622))
|
||||
|
||||
### Changed
|
||||
### Fixed
|
||||
|
@ -76,9 +76,6 @@ uio = []
|
||||
users = ["features"]
|
||||
zerocopy = ["fs", "uio"]
|
||||
|
||||
[target.'cfg(target_os = "dragonfly")'.build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert-impl = "0.1"
|
||||
lazy_static = "1.2"
|
||||
|
@ -201,3 +201,18 @@ feature! {
|
||||
#[allow(missing_docs)]
|
||||
pub mod timerfd;
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd"
|
||||
),
|
||||
feature = "time",
|
||||
feature = "signal"
|
||||
))]
|
||||
feature! {
|
||||
#![feature = "time"]
|
||||
pub mod timer;
|
||||
}
|
||||
|
@ -1085,6 +1085,11 @@ mod sigevent {
|
||||
pub fn sigevent(&self) -> libc::sigevent {
|
||||
self.sigevent
|
||||
}
|
||||
|
||||
/// Returns a mutable pointer to the `sigevent` wrapped by `self`
|
||||
pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent {
|
||||
&mut self.sigevent
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a libc::sigevent> for SigEvent {
|
||||
|
123
src/sys/time.rs
123
src/sys/time.rs
@ -5,6 +5,129 @@ use libc::{timespec, timeval};
|
||||
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
|
||||
pub use libc::{time_t, suseconds_t};
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "time", any(target_os = "android", target_os = "linux")),
|
||||
all(
|
||||
any(
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd"
|
||||
),
|
||||
feature = "time",
|
||||
feature = "signal"
|
||||
)
|
||||
))]
|
||||
pub(crate) mod timer {
|
||||
use crate::sys::time::TimeSpec;
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct TimerSpec(libc::itimerspec);
|
||||
|
||||
impl TimerSpec {
|
||||
pub const fn none() -> Self {
|
||||
Self(libc::itimerspec {
|
||||
it_interval: libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<libc::itimerspec> for TimerSpec {
|
||||
fn as_mut(&mut self) -> &mut libc::itimerspec {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<libc::itimerspec> for TimerSpec {
|
||||
fn as_ref(&self) -> &libc::itimerspec {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expiration> for TimerSpec {
|
||||
fn from(expiration: Expiration) -> TimerSpec {
|
||||
match expiration {
|
||||
Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
|
||||
it_interval: libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: *t.as_ref(),
|
||||
}),
|
||||
Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
|
||||
it_interval: *interval.as_ref(),
|
||||
it_value: *start.as_ref(),
|
||||
}),
|
||||
Expiration::Interval(t) => TimerSpec(libc::itimerspec {
|
||||
it_interval: *t.as_ref(),
|
||||
it_value: *t.as_ref(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration allowing the definition of the expiration time of an alarm,
|
||||
/// recurring or not.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Expiration {
|
||||
/// Alarm will trigger once after the time given in `TimeSpec`
|
||||
OneShot(TimeSpec),
|
||||
/// Alarm will trigger after a specified delay and then every interval of
|
||||
/// time.
|
||||
IntervalDelayed(TimeSpec, TimeSpec),
|
||||
/// Alarm will trigger every specified interval of time.
|
||||
Interval(TimeSpec),
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
bitflags! {
|
||||
/// Flags that are used for arming the timer.
|
||||
pub struct TimerSetTimeFlags: libc::c_int {
|
||||
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly", target_os = "illumos"))]
|
||||
bitflags! {
|
||||
/// Flags that are used for arming the timer.
|
||||
pub struct TimerSetTimeFlags: libc::c_int {
|
||||
const TFD_TIMER_ABSTIME = libc::TIMER_ABSTIME;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimerSpec> for Expiration {
|
||||
fn from(timerspec: TimerSpec) -> Expiration {
|
||||
match timerspec {
|
||||
TimerSpec(libc::itimerspec {
|
||||
it_interval:
|
||||
libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: ts,
|
||||
}) => Expiration::OneShot(ts.into()),
|
||||
TimerSpec(libc::itimerspec {
|
||||
it_interval: int_ts,
|
||||
it_value: val_ts,
|
||||
}) => {
|
||||
if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
|
||||
Expiration::Interval(int_ts.into())
|
||||
} else {
|
||||
Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimeValLike: Sized {
|
||||
#[inline]
|
||||
fn zero() -> Self {
|
||||
|
175
src/sys/timer.rs
Normal file
175
src/sys/timer.rs
Normal file
@ -0,0 +1,175 @@
|
||||
//! Timer API via signals.
|
||||
//!
|
||||
//! Timer is a POSIX API to create timers and get expiration notifications
|
||||
//! through queued Unix signals, for the current process. This is similar to
|
||||
//! Linux's timerfd mechanism, except that API is specific to Linux and makes
|
||||
//! use of file polling.
|
||||
//!
|
||||
//! For more documentation, please read [timer_create](https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html).
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Create an interval timer that signals SIGALARM every 250 milliseconds.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use nix::sys::signal::{self, SigEvent, SigHandler, SigevNotify, Signal};
|
||||
//! use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags};
|
||||
//! use nix::time::ClockId;
|
||||
//! use std::convert::TryFrom;
|
||||
//! use std::sync::atomic::{AtomicU64, Ordering};
|
||||
//! use std::thread::yield_now;
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! const SIG: Signal = Signal::SIGALRM;
|
||||
//! static ALARMS: AtomicU64 = AtomicU64::new(0);
|
||||
//!
|
||||
//! extern "C" fn handle_alarm(signal: libc::c_int) {
|
||||
//! let signal = Signal::try_from(signal).unwrap();
|
||||
//! if signal == SIG {
|
||||
//! ALARMS.fetch_add(1, Ordering::Relaxed);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let clockid = ClockId::CLOCK_MONOTONIC;
|
||||
//! let sigevent = SigEvent::new(SigevNotify::SigevSignal {
|
||||
//! signal: SIG,
|
||||
//! si_value: 0,
|
||||
//! });
|
||||
//!
|
||||
//! let mut timer = Timer::new(clockid, sigevent).unwrap();
|
||||
//! let expiration = Expiration::Interval(Duration::from_millis(250).into());
|
||||
//! let flags = TimerSetTimeFlags::empty();
|
||||
//! timer.set(expiration, flags).expect("could not set timer");
|
||||
//!
|
||||
//! let handler = SigHandler::Handler(handle_alarm);
|
||||
//! unsafe { signal::signal(SIG, handler) }.unwrap();
|
||||
//!
|
||||
//! loop {
|
||||
//! let alarms = ALARMS.load(Ordering::Relaxed);
|
||||
//! if alarms >= 10 {
|
||||
//! println!("total alarms handled: {}", alarms);
|
||||
//! break;
|
||||
//! }
|
||||
//! yield_now()
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
use crate::sys::signal::SigEvent;
|
||||
use crate::sys::time::timer::TimerSpec;
|
||||
pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags};
|
||||
use crate::time::ClockId;
|
||||
use crate::{errno::Errno, Result};
|
||||
use core::mem;
|
||||
|
||||
/// A Unix signal per-process timer.
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Timer(libc::timer_t);
|
||||
|
||||
impl Timer {
|
||||
/// Creates a new timer based on the clock defined by `clockid`. The details
|
||||
/// of the signal and its handler are defined by the passed `sigevent`.
|
||||
pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result<Self> {
|
||||
let mut timer_id: mem::MaybeUninit<libc::timer_t> = mem::MaybeUninit::uninit();
|
||||
Errno::result(unsafe {
|
||||
libc::timer_create(
|
||||
clockid.as_raw(),
|
||||
sigevent.as_mut_ptr(),
|
||||
timer_id.as_mut_ptr(),
|
||||
)
|
||||
})
|
||||
.map(|_| {
|
||||
// SAFETY: libc::timer_create is responsible for initializing
|
||||
// timer_id.
|
||||
unsafe { Self(timer_id.assume_init()) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Set a new alarm on the timer.
|
||||
///
|
||||
/// # Types of alarm
|
||||
///
|
||||
/// There are 3 types of alarms you can set:
|
||||
///
|
||||
/// - one shot: the alarm will trigger once after the specified amount of
|
||||
/// time.
|
||||
/// Example: I want an alarm to go off in 60s and then disable itself.
|
||||
///
|
||||
/// - interval: the alarm will trigger every specified interval of time.
|
||||
/// Example: I want an alarm to go off every 60s. The alarm will first
|
||||
/// go off 60s after I set it and every 60s after that. The alarm will
|
||||
/// not disable itself.
|
||||
///
|
||||
/// - interval delayed: the alarm will trigger after a certain amount of
|
||||
/// time and then trigger at a specified interval.
|
||||
/// Example: I want an alarm to go off every 60s but only start in 1h.
|
||||
/// The alarm will first trigger 1h after I set it and then every 60s
|
||||
/// after that. The alarm will not disable itself.
|
||||
///
|
||||
/// # Relative vs absolute alarm
|
||||
///
|
||||
/// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
|
||||
/// to the `Expiration` you want is relative. If however you want an alarm
|
||||
/// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
|
||||
/// Then the one shot TimeSpec and the delay TimeSpec of the delayed
|
||||
/// interval are going to be interpreted as absolute.
|
||||
///
|
||||
/// # Disabling alarms
|
||||
///
|
||||
/// Note: Only one alarm can be set for any given timer. Setting a new alarm
|
||||
/// actually removes the previous one.
|
||||
///
|
||||
/// Note: Setting a one shot alarm with a 0s TimeSpec disable the alarm
|
||||
/// altogether.
|
||||
pub fn set(&mut self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
|
||||
let timerspec: TimerSpec = expiration.into();
|
||||
Errno::result(unsafe {
|
||||
libc::timer_settime(
|
||||
self.0,
|
||||
flags.bits(),
|
||||
timerspec.as_ref(),
|
||||
core::ptr::null_mut(),
|
||||
)
|
||||
})
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
/// Get the parameters for the alarm currently set, if any.
|
||||
pub fn get(&self) -> Result<Option<Expiration>> {
|
||||
let mut timerspec = TimerSpec::none();
|
||||
Errno::result(unsafe { libc::timer_gettime(self.0, timerspec.as_mut()) }).map(|_| {
|
||||
if timerspec.as_ref().it_interval.tv_sec == 0
|
||||
&& timerspec.as_ref().it_interval.tv_nsec == 0
|
||||
&& timerspec.as_ref().it_value.tv_sec == 0
|
||||
&& timerspec.as_ref().it_value.tv_nsec == 0
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(timerspec.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the number of timers that have overrun
|
||||
///
|
||||
/// Each timer is able to queue one signal to the process at a time, meaning
|
||||
/// if the signal is not handled before the next expiration the timer has
|
||||
/// 'overrun'. This function returns how many times that has happened to
|
||||
/// this timer, up to `libc::DELAYTIMER_MAX`. If more than the maximum
|
||||
/// number of overruns have happened the return is capped to the maximum.
|
||||
pub fn overruns(&self) -> i32 {
|
||||
unsafe { libc::timer_getoverrun(self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timer {
|
||||
fn drop(&mut self) {
|
||||
if !std::thread::panicking() {
|
||||
let result = Errno::result(unsafe { libc::timer_delete(self.0) });
|
||||
if let Err(Errno::EINVAL) = result {
|
||||
panic!("close of Timer encountered EINVAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -28,10 +28,10 @@
|
||||
//! // We wait for the timer to expire.
|
||||
//! timer.wait().unwrap();
|
||||
//! ```
|
||||
use crate::sys::time::TimeSpec;
|
||||
use crate::sys::time::timer::TimerSpec;
|
||||
pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags};
|
||||
use crate::unistd::read;
|
||||
use crate::{errno::Errno, Result};
|
||||
use bitflags::bitflags;
|
||||
use libc::c_int;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
|
||||
@ -77,93 +77,6 @@ libc_bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags that are used for arming the timer.
|
||||
pub struct TimerSetTimeFlags: libc::c_int {
|
||||
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct TimerSpec(libc::itimerspec);
|
||||
|
||||
impl TimerSpec {
|
||||
pub const fn none() -> Self {
|
||||
Self(libc::itimerspec {
|
||||
it_interval: libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<libc::itimerspec> for TimerSpec {
|
||||
fn as_ref(&self) -> &libc::itimerspec {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expiration> for TimerSpec {
|
||||
fn from(expiration: Expiration) -> TimerSpec {
|
||||
match expiration {
|
||||
Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
|
||||
it_interval: libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: *t.as_ref(),
|
||||
}),
|
||||
Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
|
||||
it_interval: *interval.as_ref(),
|
||||
it_value: *start.as_ref(),
|
||||
}),
|
||||
Expiration::Interval(t) => TimerSpec(libc::itimerspec {
|
||||
it_interval: *t.as_ref(),
|
||||
it_value: *t.as_ref(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimerSpec> for Expiration {
|
||||
fn from(timerspec: TimerSpec) -> Expiration {
|
||||
match timerspec {
|
||||
TimerSpec(libc::itimerspec {
|
||||
it_interval:
|
||||
libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
},
|
||||
it_value: ts,
|
||||
}) => Expiration::OneShot(ts.into()),
|
||||
TimerSpec(libc::itimerspec {
|
||||
it_interval: int_ts,
|
||||
it_value: val_ts,
|
||||
}) => {
|
||||
if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
|
||||
Expiration::Interval(int_ts.into())
|
||||
} else {
|
||||
Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration allowing the definition of the expiration time of an alarm,
|
||||
/// recurring or not.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Expiration {
|
||||
OneShot(TimeSpec),
|
||||
IntervalDelayed(TimeSpec, TimeSpec),
|
||||
Interval(TimeSpec),
|
||||
}
|
||||
|
||||
impl TimerFd {
|
||||
/// Creates a new timer based on the clock defined by `clockid`. The
|
||||
/// underlying fd can be assigned specific flags with `flags` (CLOEXEC,
|
||||
@ -181,7 +94,7 @@ impl TimerFd {
|
||||
///
|
||||
/// - one shot: the alarm will trigger once after the specified amount of
|
||||
/// time.
|
||||
/// Example: I want an alarm to go off in 60s and then disables itself.
|
||||
/// Example: I want an alarm to go off in 60s and then disable itself.
|
||||
///
|
||||
/// - interval: the alarm will trigger every specified interval of time.
|
||||
/// Example: I want an alarm to go off every 60s. The alarm will first
|
||||
@ -225,13 +138,11 @@ impl TimerFd {
|
||||
/// Get the parameters for the alarm currently set, if any.
|
||||
pub fn get(&self) -> Result<Option<Expiration>> {
|
||||
let mut timerspec = TimerSpec::none();
|
||||
let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0;
|
||||
|
||||
Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| {
|
||||
if timerspec.0.it_interval.tv_sec == 0
|
||||
&& timerspec.0.it_interval.tv_nsec == 0
|
||||
&& timerspec.0.it_value.tv_sec == 0
|
||||
&& timerspec.0.it_value.tv_nsec == 0
|
||||
Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec.as_mut()) }).map(|_| {
|
||||
if timerspec.as_ref().it_interval.tv_sec == 0
|
||||
&& timerspec.as_ref().it_interval.tv_nsec == 0
|
||||
&& timerspec.as_ref().it_value.tv_sec == 0
|
||||
&& timerspec.as_ref().it_value.tv_nsec == 0
|
||||
{
|
||||
None
|
||||
} else {
|
||||
@ -259,7 +170,7 @@ impl TimerFd {
|
||||
pub fn wait(&self) -> Result<()> {
|
||||
while let Err(e) = read(self.fd, &mut [0u8; 8]) {
|
||||
if e != Errno::EINTR {
|
||||
return Err(e)
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,9 +181,7 @@ impl TimerFd {
|
||||
impl Drop for TimerFd {
|
||||
fn drop(&mut self) {
|
||||
if !std::thread::panicking() {
|
||||
let result = Errno::result(unsafe {
|
||||
libc::close(self.fd)
|
||||
});
|
||||
let result = Errno::result(unsafe { libc::close(self.fd) });
|
||||
if let Err(Errno::EBADF) = result {
|
||||
panic!("close of TimerFd encountered EBADF");
|
||||
}
|
||||
|
11
test/test.rs
11
test/test.rs
@ -41,6 +41,17 @@ mod test_sendfile;
|
||||
mod test_stat;
|
||||
mod test_time;
|
||||
mod test_unistd;
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd"
|
||||
),
|
||||
feature = "time",
|
||||
feature = "signal"
|
||||
))]
|
||||
mod test_timer;
|
||||
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::path::PathBuf;
|
||||
|
91
test/test_timer.rs
Normal file
91
test/test_timer.rs
Normal file
@ -0,0 +1,91 @@
|
||||
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();
|
||||
|
||||
//
|
||||
// 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(Duration::from_millis(250).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(Duration::from_millis(500));
|
||||
if ALARM_CALLED.load(Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
if starttime.elapsed() > Duration::from_secs(3) {
|
||||
panic!("Timeout waiting for SIGALRM");
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
unsafe {
|
||||
sigaction(SIG, &old_handler).expect("unable to reset signal handler");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user