Skip to content

Commit d13d147

Browse files
committed
Introduce timer_* support
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 commit isn't too far off. Resolves nix-rust#1424 Signed-off-by: Brian L. Troutwine <[email protected]>
1 parent a392647 commit d13d147

File tree

4 files changed

+132
-1
lines changed

4 files changed

+132
-1
lines changed

src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ feature! {
143143
#[allow(missing_docs)]
144144
pub mod time;
145145
}
146+
feature! {
147+
#![feature = "time"]
148+
pub mod timer;
149+
}
146150
// This can be implemented for other platforms as soon as libc
147151
// provides bindings for them.
148152
#[cfg(all(target_os = "linux",

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_raw_mut(&mut self) -> *mut libc::sigevent {
1091+
&mut self.sigevent
1092+
}
10881093
}
10891094

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

src/sys/timerfd.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ bitflags! {
8585
}
8686

8787
#[derive(Debug, Clone, Copy)]
88-
struct TimerSpec(libc::itimerspec);
88+
pub(crate) struct TimerSpec(libc::itimerspec);
8989

9090
impl TimerSpec {
9191
pub const fn none() -> Self {
@@ -102,6 +102,12 @@ impl TimerSpec {
102102
}
103103
}
104104

105+
impl AsMut<libc::itimerspec> for TimerSpec {
106+
fn as_mut(&mut self) -> &mut libc::itimerspec {
107+
&mut self.0
108+
}
109+
}
110+
105111
impl AsRef<libc::itimerspec> for TimerSpec {
106112
fn as_ref(&self) -> &libc::itimerspec {
107113
&self.0

src/timer.rs

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//! Timer API via signals.
2+
//!
3+
//! Timer is a POSIX API to create timers and get expiration notifications
4+
//! through signals.
5+
//!
6+
//! For more documentation, please read [timer_create(3p)](https://man7.org/linux/man-pages/man3/timer_create.3p.html).
7+
use crate::sys::timerfd::{TimerSetTimeFlags, TimerSpec};
8+
use crate::sys::{signal::SigEvent, timerfd::Expiration};
9+
use crate::time::ClockId;
10+
use crate::{errno::Errno, Result};
11+
use core::mem;
12+
13+
/// The maximum value that [`Timer::overruns`] will return.
14+
pub const DELAYTIMER_MAX: i32 = libc::_SC_DELAYTIMER_MAX as i32;
15+
16+
/// A per-process timer
17+
#[derive(Debug)]
18+
pub struct Timer {
19+
timer_id: libc::timer_t,
20+
}
21+
22+
impl Timer {
23+
/// Creates a new timer based on the clock defined by `clockid`. The details
24+
/// of the signal and its handler are defined by the passed `sigevent`.
25+
pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result<Self> {
26+
let mut timer_id: libc::timer_t = unsafe { mem::zeroed::<libc::timer_t>() };
27+
Errno::result(unsafe {
28+
libc::timer_create(clockid.as_raw(), sigevent.as_raw_mut(), &mut timer_id)
29+
})
30+
.map(|_| Self { timer_id })
31+
}
32+
33+
/// Set a new alarm on the timer.
34+
///
35+
/// # Types of alarm
36+
///
37+
/// There are 3 types of alarms you can set:
38+
///
39+
/// - one shot: the alarm will trigger once after the specified amount of
40+
/// time.
41+
/// Example: I want an alarm to go off in 60s and then disables itself.
42+
///
43+
/// - interval: the alarm will trigger every specified interval of time.
44+
/// Example: I want an alarm to go off every 60s. The alarm will first
45+
/// go off 60s after I set it and every 60s after that. The alarm will
46+
/// not disable itself.
47+
///
48+
/// - interval delayed: the alarm will trigger after a certain amount of
49+
/// time and then trigger at a specified interval.
50+
/// Example: I want an alarm to go off every 60s but only start in 1h.
51+
/// The alarm will first trigger 1h after I set it and then every 60s
52+
/// after that. The alarm will not disable itself.
53+
///
54+
/// # Relative vs absolute alarm
55+
///
56+
/// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
57+
/// to the `Expiration` you want is relative. If however you want an alarm
58+
/// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
59+
/// Then the one shot TimeSpec and the delay TimeSpec of the delayed
60+
/// interval are going to be interpreted as absolute.
61+
///
62+
/// # Disabling alarms
63+
///
64+
/// Note: Only one alarm can be set for any given timer. Setting a new alarm
65+
/// actually removes the previous one.
66+
///
67+
/// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm
68+
/// altogether.
69+
pub fn set(&mut self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
70+
let timerspec: TimerSpec = expiration.into();
71+
Errno::result(unsafe {
72+
libc::timer_settime(
73+
self.timer_id,
74+
flags.bits(),
75+
timerspec.as_ref(),
76+
core::ptr::null_mut(),
77+
)
78+
})
79+
.map(drop)
80+
}
81+
82+
/// Get the parameters for the alarm currently set, if any.
83+
pub fn get(&self) -> Result<Option<Expiration>> {
84+
let mut timerspec = TimerSpec::none();
85+
Errno::result(unsafe { libc::timer_gettime(self.timer_id, timerspec.as_mut()) }).map(|_| {
86+
if timerspec.as_ref().it_interval.tv_sec == 0
87+
&& timerspec.as_ref().it_interval.tv_nsec == 0
88+
&& timerspec.as_ref().it_value.tv_sec == 0
89+
&& timerspec.as_ref().it_value.tv_nsec == 0
90+
{
91+
None
92+
} else {
93+
Some(timerspec.into())
94+
}
95+
})
96+
}
97+
98+
/// Return the number of timers that have overrun
99+
///
100+
/// An overrun timer is one which expires while its related signal is still
101+
/// queued. TODO explain better
102+
pub fn overruns(&self) -> i32 {
103+
unsafe { libc::timer_getoverrun(self.timer_id) }
104+
}
105+
}
106+
107+
impl Drop for Timer {
108+
fn drop(&mut self) {
109+
if !std::thread::panicking() {
110+
let result = Errno::result(unsafe { libc::timer_delete(self.timer_id) });
111+
if let Err(Errno::EINVAL) = result {
112+
panic!("close of Timer encountered EINVAL");
113+
}
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)