summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/sys/mod.rs15
-rw-r--r--src/sys/signal.rs5
-rw-r--r--src/sys/time.rs123
-rw-r--r--src/sys/timer.rs175
-rw-r--r--src/sys/timerfd.rs111
-rw-r--r--test/test.rs11
-rw-r--r--test/test_timer.rs91
8 files changed, 432 insertions, 101 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c4516ae..3b097d6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/src/sys/mod.rs b/src/sys/mod.rs
index 16ba9e0a..654a4d87 100644
--- a/src/sys/mod.rs
+++ b/src/sys/mod.rs
@@ -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;
+}
diff --git a/src/sys/signal.rs b/src/sys/signal.rs
index b655604d..9750d890 100644
--- a/src/sys/signal.rs
+++ b/src/sys/signal.rs
@@ -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 {
diff --git a/src/sys/time.rs b/src/sys/time.rs
index ac424718..1e62b76a 100644
--- a/src/sys/time.rs
+++ b/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 {
diff --git a/src/sys/timer.rs b/src/sys/timer.rs
new file mode 100644
index 00000000..349346bb
--- /dev/null
+++ b/src/sys/timer.rs
@@ -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");
+ }
+ }
+ }
+}
diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs
index 705a3c4d..bc5a75d4 100644
--- a/src/sys/timerfd.rs
+++ b/src/sys/timerfd.rs
@@ -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");
}
diff --git a/test/test.rs b/test/test.rs
index 922caa5f..83cb4645 100644
--- a/test/test.rs
+++ b/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;
diff --git a/test/test_timer.rs b/test/test_timer.rs
new file mode 100644
index 00000000..31a740b0
--- /dev/null
+++ b/test/test_timer.rs
@@ -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");
+ }
+}