Skip to content

Commit

Permalink
Introduce timer_* support
Browse files Browse the repository at this point in the history
This commit adds support for the signal timer mechanism in POSIX, the
mirror to timerfd on Linux.

Resolves #1424

Signed-off-by: Brian L. Troutwine <brian@troutwine.us>
  • Loading branch information
blt committed Dec 31, 2021
1 parent b9eb197 commit 77febe0
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 101 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions src/sys/mod.rs
Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions src/sys/signal.rs
Expand Up @@ -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 {
Expand Down
123 changes: 123 additions & 0 deletions src/sys/time.rs
Expand Up @@ -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 {
Expand Down
175 changes: 175 additions & 0 deletions 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");
}
}
}
}

0 comments on commit 77febe0

Please sign in to comment.