Skip to content

Commit d0fa578

Browse files
committed
Introduce timer_* support
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 <[email protected]>
1 parent b9eb197 commit d0fa578

File tree

8 files changed

+432
-101
lines changed

8 files changed

+432
-101
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
2727
(#[1619](https://github.com/nix-rust/nix/pull/1619))
2828
- Added the `TxTime` sockopt and control message.
2929
(#[1564](https://github.com/nix-rust/nix/pull/1564))
30+
- Added POSIX per-process timer support
31+
(#[1622](https://github.com/nix-rust/nix/pull/1622))
3032

3133
### Changed
3234
### Fixed

src/sys/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,18 @@ feature! {
201201
#[allow(missing_docs)]
202202
pub mod timerfd;
203203
}
204+
205+
#[cfg(all(
206+
any(
207+
target_os = "freebsd",
208+
target_os = "illumos",
209+
target_os = "linux",
210+
target_os = "netbsd"
211+
),
212+
feature = "time",
213+
feature = "signal"
214+
))]
215+
feature! {
216+
#![feature = "time"]
217+
pub mod timer;
218+
}

src/sys/signal.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,11 @@ mod sigevent {
10851085
pub fn sigevent(&self) -> libc::sigevent {
10861086
self.sigevent
10871087
}
1088+
1089+
/// Returns a mutable pointer to the `sigevent` wrapped by `self`
1090+
pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent {
1091+
&mut self.sigevent
1092+
}
10881093
}
10891094

10901095
impl<'a> From<&'a libc::sigevent> for SigEvent {

src/sys/time.rs

+123
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,129 @@ use libc::{timespec, timeval};
55
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
66
pub use libc::{time_t, suseconds_t};
77

8+
#[cfg(any(
9+
all(feature = "time", any(target_os = "android", target_os = "linux")),
10+
all(
11+
any(
12+
target_os = "freebsd",
13+
target_os = "illumos",
14+
target_os = "linux",
15+
target_os = "netbsd"
16+
),
17+
feature = "time",
18+
feature = "signal"
19+
)
20+
))]
21+
pub(crate) mod timer {
22+
use crate::sys::time::TimeSpec;
23+
use bitflags::bitflags;
24+
25+
#[derive(Debug, Clone, Copy)]
26+
pub(crate) struct TimerSpec(libc::itimerspec);
27+
28+
impl TimerSpec {
29+
pub const fn none() -> Self {
30+
Self(libc::itimerspec {
31+
it_interval: libc::timespec {
32+
tv_sec: 0,
33+
tv_nsec: 0,
34+
},
35+
it_value: libc::timespec {
36+
tv_sec: 0,
37+
tv_nsec: 0,
38+
},
39+
})
40+
}
41+
}
42+
43+
impl AsMut<libc::itimerspec> for TimerSpec {
44+
fn as_mut(&mut self) -> &mut libc::itimerspec {
45+
&mut self.0
46+
}
47+
}
48+
49+
impl AsRef<libc::itimerspec> for TimerSpec {
50+
fn as_ref(&self) -> &libc::itimerspec {
51+
&self.0
52+
}
53+
}
54+
55+
impl From<Expiration> for TimerSpec {
56+
fn from(expiration: Expiration) -> TimerSpec {
57+
match expiration {
58+
Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
59+
it_interval: libc::timespec {
60+
tv_sec: 0,
61+
tv_nsec: 0,
62+
},
63+
it_value: *t.as_ref(),
64+
}),
65+
Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
66+
it_interval: *interval.as_ref(),
67+
it_value: *start.as_ref(),
68+
}),
69+
Expiration::Interval(t) => TimerSpec(libc::itimerspec {
70+
it_interval: *t.as_ref(),
71+
it_value: *t.as_ref(),
72+
}),
73+
}
74+
}
75+
}
76+
77+
/// An enumeration allowing the definition of the expiration time of an alarm,
78+
/// recurring or not.
79+
#[derive(Debug, Clone, Copy, PartialEq)]
80+
pub enum Expiration {
81+
/// Alarm will trigger once after the time given in `TimeSpec`
82+
OneShot(TimeSpec),
83+
/// Alarm will trigger after a specified delay and then every interval of
84+
/// time.
85+
IntervalDelayed(TimeSpec, TimeSpec),
86+
/// Alarm will trigger every specified interval of time.
87+
Interval(TimeSpec),
88+
}
89+
90+
#[cfg(any(target_os = "android", target_os = "linux"))]
91+
bitflags! {
92+
/// Flags that are used for arming the timer.
93+
pub struct TimerSetTimeFlags: libc::c_int {
94+
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
95+
}
96+
}
97+
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly", target_os = "illumos"))]
98+
bitflags! {
99+
/// Flags that are used for arming the timer.
100+
pub struct TimerSetTimeFlags: libc::c_int {
101+
const TFD_TIMER_ABSTIME = libc::TIMER_ABSTIME;
102+
}
103+
}
104+
105+
impl From<TimerSpec> for Expiration {
106+
fn from(timerspec: TimerSpec) -> Expiration {
107+
match timerspec {
108+
TimerSpec(libc::itimerspec {
109+
it_interval:
110+
libc::timespec {
111+
tv_sec: 0,
112+
tv_nsec: 0,
113+
},
114+
it_value: ts,
115+
}) => Expiration::OneShot(ts.into()),
116+
TimerSpec(libc::itimerspec {
117+
it_interval: int_ts,
118+
it_value: val_ts,
119+
}) => {
120+
if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
121+
Expiration::Interval(int_ts.into())
122+
} else {
123+
Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
130+
8131
pub trait TimeValLike: Sized {
9132
#[inline]
10133
fn zero() -> Self {

src/sys/timer.rs

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//! Timer API via signals.
2+
//!
3+
//! Timer is a POSIX API to create timers and get expiration notifications
4+
//! through queued Unix signals, for the current process. This is similar to
5+
//! Linux's timerfd mechanism, except that API is specific to Linux and makes
6+
//! use of file polling.
7+
//!
8+
//! For more documentation, please read [timer_create](https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html).
9+
//!
10+
//! # Examples
11+
//!
12+
//! Create an interval timer that signals SIGALARM every 250 milliseconds.
13+
//!
14+
//! ```no_run
15+
//! use nix::sys::signal::{self, SigEvent, SigHandler, SigevNotify, Signal};
16+
//! use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags};
17+
//! use nix::time::ClockId;
18+
//! use std::convert::TryFrom;
19+
//! use std::sync::atomic::{AtomicU64, Ordering};
20+
//! use std::thread::yield_now;
21+
//! use std::time::Duration;
22+
//!
23+
//! const SIG: Signal = Signal::SIGALRM;
24+
//! static ALARMS: AtomicU64 = AtomicU64::new(0);
25+
//!
26+
//! extern "C" fn handle_alarm(signal: libc::c_int) {
27+
//! let signal = Signal::try_from(signal).unwrap();
28+
//! if signal == SIG {
29+
//! ALARMS.fetch_add(1, Ordering::Relaxed);
30+
//! }
31+
//! }
32+
//!
33+
//! fn main() {
34+
//! let clockid = ClockId::CLOCK_MONOTONIC;
35+
//! let sigevent = SigEvent::new(SigevNotify::SigevSignal {
36+
//! signal: SIG,
37+
//! si_value: 0,
38+
//! });
39+
//!
40+
//! let mut timer = Timer::new(clockid, sigevent).unwrap();
41+
//! let expiration = Expiration::Interval(Duration::from_millis(250).into());
42+
//! let flags = TimerSetTimeFlags::empty();
43+
//! timer.set(expiration, flags).expect("could not set timer");
44+
//!
45+
//! let handler = SigHandler::Handler(handle_alarm);
46+
//! unsafe { signal::signal(SIG, handler) }.unwrap();
47+
//!
48+
//! loop {
49+
//! let alarms = ALARMS.load(Ordering::Relaxed);
50+
//! if alarms >= 10 {
51+
//! println!("total alarms handled: {}", alarms);
52+
//! break;
53+
//! }
54+
//! yield_now()
55+
//! }
56+
//! }
57+
//! ```
58+
use crate::sys::signal::SigEvent;
59+
use crate::sys::time::timer::TimerSpec;
60+
pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags};
61+
use crate::time::ClockId;
62+
use crate::{errno::Errno, Result};
63+
use core::mem;
64+
65+
/// A Unix signal per-process timer.
66+
#[derive(Debug)]
67+
#[repr(transparent)]
68+
pub struct Timer(libc::timer_t);
69+
70+
impl Timer {
71+
/// Creates a new timer based on the clock defined by `clockid`. The details
72+
/// of the signal and its handler are defined by the passed `sigevent`.
73+
pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result<Self> {
74+
let mut timer_id: mem::MaybeUninit<libc::timer_t> = mem::MaybeUninit::uninit();
75+
Errno::result(unsafe {
76+
libc::timer_create(
77+
clockid.as_raw(),
78+
sigevent.as_mut_ptr(),
79+
timer_id.as_mut_ptr(),
80+
)
81+
})
82+
.map(|_| {
83+
// SAFETY: libc::timer_create is responsible for initializing
84+
// timer_id.
85+
unsafe { Self(timer_id.assume_init()) }
86+
})
87+
}
88+
89+
/// Set a new alarm on the timer.
90+
///
91+
/// # Types of alarm
92+
///
93+
/// There are 3 types of alarms you can set:
94+
///
95+
/// - one shot: the alarm will trigger once after the specified amount of
96+
/// time.
97+
/// Example: I want an alarm to go off in 60s and then disable itself.
98+
///
99+
/// - interval: the alarm will trigger every specified interval of time.
100+
/// Example: I want an alarm to go off every 60s. The alarm will first
101+
/// go off 60s after I set it and every 60s after that. The alarm will
102+
/// not disable itself.
103+
///
104+
/// - interval delayed: the alarm will trigger after a certain amount of
105+
/// time and then trigger at a specified interval.
106+
/// Example: I want an alarm to go off every 60s but only start in 1h.
107+
/// The alarm will first trigger 1h after I set it and then every 60s
108+
/// after that. The alarm will not disable itself.
109+
///
110+
/// # Relative vs absolute alarm
111+
///
112+
/// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
113+
/// to the `Expiration` you want is relative. If however you want an alarm
114+
/// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
115+
/// Then the one shot TimeSpec and the delay TimeSpec of the delayed
116+
/// interval are going to be interpreted as absolute.
117+
///
118+
/// # Disabling alarms
119+
///
120+
/// Note: Only one alarm can be set for any given timer. Setting a new alarm
121+
/// actually removes the previous one.
122+
///
123+
/// Note: Setting a one shot alarm with a 0s TimeSpec disable the alarm
124+
/// altogether.
125+
pub fn set(&mut self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
126+
let timerspec: TimerSpec = expiration.into();
127+
Errno::result(unsafe {
128+
libc::timer_settime(
129+
self.0,
130+
flags.bits(),
131+
timerspec.as_ref(),
132+
core::ptr::null_mut(),
133+
)
134+
})
135+
.map(drop)
136+
}
137+
138+
/// Get the parameters for the alarm currently set, if any.
139+
pub fn get(&self) -> Result<Option<Expiration>> {
140+
let mut timerspec = TimerSpec::none();
141+
Errno::result(unsafe { libc::timer_gettime(self.0, timerspec.as_mut()) }).map(|_| {
142+
if timerspec.as_ref().it_interval.tv_sec == 0
143+
&& timerspec.as_ref().it_interval.tv_nsec == 0
144+
&& timerspec.as_ref().it_value.tv_sec == 0
145+
&& timerspec.as_ref().it_value.tv_nsec == 0
146+
{
147+
None
148+
} else {
149+
Some(timerspec.into())
150+
}
151+
})
152+
}
153+
154+
/// Return the number of timers that have overrun
155+
///
156+
/// Each timer is able to queue one signal to the process at a time, meaning
157+
/// if the signal is not handled before the next expiration the timer has
158+
/// 'overrun'. This function returns how many times that has happened to
159+
/// this timer, up to `libc::DELAYTIMER_MAX`. If more than the maximum
160+
/// number of overruns have happened the return is capped to the maximum.
161+
pub fn overruns(&self) -> i32 {
162+
unsafe { libc::timer_getoverrun(self.0) }
163+
}
164+
}
165+
166+
impl Drop for Timer {
167+
fn drop(&mut self) {
168+
if !std::thread::panicking() {
169+
let result = Errno::result(unsafe { libc::timer_delete(self.0) });
170+
if let Err(Errno::EINVAL) = result {
171+
panic!("close of Timer encountered EINVAL");
172+
}
173+
}
174+
}
175+
}

0 commit comments

Comments
 (0)