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. 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>
  • Loading branch information
blt committed Dec 28, 2021
1 parent c77a872 commit 6edeef3
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 106 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -25,6 +25,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(#[1547](https://github.com/nix-rust/nix/pull/1547))
- Added getter methods to `MqAttr` struct
(#[1619](https://github.com/nix-rust/nix/pull/1619))
- Added `timer` support
(#[1620](https://github.com/nix-rust/nix/pull/1620))

### Changed
### Fixed
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Expand Up @@ -39,7 +39,7 @@ default = [
"acct", "aio", "dir", "env", "event", "features", "fs",
"hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue",
"net", "personality", "poll", "process", "pthread", "ptrace", "quota",
"reboot", "resource", "sched", "signal", "socket", "term", "time",
"reboot", "resource", "sched", "signal", "socket", "term", "time", "timer",
"ucontext", "uio", "users", "zerocopy",
]

Expand Down Expand Up @@ -71,6 +71,7 @@ signal = ["process"]
socket = []
term = []
time = []
timer = ["signal", "time"]
ucontext = ["signal"]
uio = []
users = ["features"]
Expand Down
9 changes: 9 additions & 0 deletions src/sys/mod.rs
Expand Up @@ -201,3 +201,12 @@ feature! {
#[allow(missing_docs)]
pub mod timerfd;
}

#[cfg(all(
any(target_os = "freebsd", target_os = "netbsd", target_os = "illumos", target_os = "linux"),
feature = "timer"
))]
feature! {
#![feature = "timer"]
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_raw_mut(&mut self) -> *mut libc::sigevent {
&mut self.sigevent
}
}

impl<'a> From<&'a libc::sigevent> for SigEvent {
Expand Down
125 changes: 120 additions & 5 deletions src/sys/time.rs
@@ -1,9 +1,124 @@
use std::{cmp, fmt, ops};
use std::time::Duration;
use std::convert::From;
#[cfg_attr(target_env = "musl", allow(deprecated))]
// https://github.com/rust-lang/libc/issues/1848
pub use libc::{suseconds_t, time_t};
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};
use std::convert::From;
use std::time::Duration;
use std::{cmp, fmt, ops};

#[cfg(all(
any(target_os = "android", target_os = "freebsd", target_os = "netbsd", target_os = "linux", target_os = "dragonfly"),
any(feature = "timer", feature = "time")
))]
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(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly")))]
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"))]
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]
Expand Down
116 changes: 116 additions & 0 deletions src/sys/timer.rs
@@ -0,0 +1,116 @@
//! Timer API via signals.
//!
//! Timer is a POSIX API to create timers and get expiration notifications
//! through signals.
//!
//! For more documentation, please read [timer_create(3p)](https://man7.org/linux/man-pages/man3/timer_create.3p.html).
use crate::sys::signal::SigEvent;
use crate::sys::time::timer::{Expiration, TimerSetTimeFlags, TimerSpec};
use crate::time::ClockId;
use crate::{errno::Errno, Result};
use core::mem;

/// The maximum value that [`Timer::overruns`] will return.
pub const DELAYTIMER_MAX: i32 = libc::_SC_DELAYTIMER_MAX as i32;

/// A per-process timer
#[derive(Debug)]
pub struct Timer {
timer_id: 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: libc::timer_t = unsafe { mem::zeroed::<libc::timer_t>() };
Errno::result(unsafe {
libc::timer_create(clockid.as_raw(), sigevent.as_raw_mut(), &mut timer_id)
})
.map(|_| Self { timer_id })
}

/// 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 disables 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 disables 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.timer_id,
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.timer_id, 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
///
/// An overrun timer is one which expires while its related signal is still
/// queued. TODO explain better
pub fn overruns(&self) -> i32 {
unsafe { libc::timer_getoverrun(self.timer_id) }
}
}

impl Drop for Timer {
fn drop(&mut self) {
if !std::thread::panicking() {
let result = Errno::result(unsafe { libc::timer_delete(self.timer_id) });
if let Err(Errno::EINVAL) = result {
panic!("close of Timer encountered EINVAL");
}
}
}
}

0 comments on commit 6edeef3

Please sign in to comment.