Skip to content

Commit

Permalink
* Ran rustfmt.
Browse files Browse the repository at this point in the history
* Added a `From` implementation for `libc::timespec` -> `TimeSpec`.
* Reworked the example with the new changes and changed the timer from 5 to 1 second.
* Added a constructor for a 0-initialized `TimerSpec`.
* Added a new method to get the timer configured expiration (based on timerfd_gettime).
* Added an helper method to unset the expiration of the timer.
* Added a `wait` method to actually read from the timer.
* Renamed `settime` into just `set`.
* Refactored the tests and added a new one that tests both the `unset` and the `get` method.
  • Loading branch information
vdagonneau-anssi committed Jun 29, 2020
1 parent 4a3f5df commit 05cd7f1
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 54 deletions.
5 changes: 5 additions & 0 deletions src/sys/time.rs
Expand Up @@ -60,6 +60,11 @@ const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64;

const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS;

impl From<timespec> for TimeSpec {
fn from(ts: timespec) -> Self {
Self(ts)
}
}

impl AsRef<timespec> for TimeSpec {
fn as_ref(&self) -> &timespec {
Expand Down
151 changes: 118 additions & 33 deletions src/sys/timerfd.rs
Expand Up @@ -16,21 +16,21 @@
//! # use nix::unistd::read;
//! #
//! // We create a new monotonic timer.
//! let fd = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
//! .unwrap();
//!
//! // We set a new one-shot timer in 5 seconds.
//! fd.settime(
//! Expiration::OneShot(TimeSpec::seconds(5)),
//! // We set a new one-shot timer in 1 seconds.
//! timer.set(
//! Expiration::OneShot(TimeSpec::seconds(1)),
//! TimerSetTimeFlags::empty()
//! ).unwrap();
//!
//!
//! // We wait for the timer to expire.
//! read(fd.as_raw_fd(), &mut [0u8; 8]).unwrap();
//! timer.wait().unwrap();
//! ```
use crate::errno::Errno;
use crate::sys::time::TimeSpec;
use crate::Result;
use crate::unistd::read;
use crate::{errno::Errno, Error, Result};
use bitflags::bitflags;
use libc::c_int;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
Expand Down Expand Up @@ -86,6 +86,21 @@ bitflags! {
#[derive(Debug, Clone, Copy)]
struct TimerSpec(libc::itimerspec);

impl TimerSpec {
pub 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
Expand All @@ -102,12 +117,10 @@ impl From<Expiration> for TimerSpec {
},
it_value: *t.as_ref(),
}),
Expiration::IntervalDelayed(start, interval) => TimerSpec(
libc::itimerspec {
it_interval: *interval.as_ref(),
it_value: *start.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(),
Expand All @@ -116,9 +129,34 @@ impl From<Expiration> for TimerSpec {
}
}

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)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Expiration {
OneShot(TimeSpec),
IntervalDelayed(TimeSpec, TimeSpec),
Expand All @@ -128,51 +166,49 @@ pub enum Expiration {
impl TimerFd {
/// Creates a new timer based on the clock defined by `clockid`. The
/// underlying fd can be assigned specific flags with `flags` (CLOEXEC,
/// NONBLOCK).
/// NONBLOCK).
pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> {
Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) })
.map(|fd| Self { fd })
}

/// Sets 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
///
/// - 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.
///
/// 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`.
/// 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 settime(&self, expiration: Expiration, flags: TimerSetTimeFlags)
-> Result<()>
{
pub fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
let timerspec: TimerSpec = expiration.into();
Errno::result(unsafe {
libc::timerfd_settime(
Expand All @@ -184,4 +220,53 @@ impl TimerFd {
})
.map(drop)
}

/// 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
{
None
} else {
Some(timerspec.into())
}
})
}

/// Remove the alarm if any is set.
pub fn unset(&self) -> Result<()> {
Errno::result(unsafe {
libc::timerfd_settime(
self.fd,
TimerSetTimeFlags::empty().bits(),
TimerSpec::none().as_ref(),
std::ptr::null_mut(),
)
})
.map(drop)
}

/// Wait for the configured alarm to expire.
///
/// Note: If the alarm is unset, then you will wait forever.
pub fn wait(&self) -> Result<()> {
loop {
if let Err(e) = read(self.fd, &mut [0u8; 8]) {
match e {
Error::Sys(Errno::EINTR) => continue,
_ => return Err(e),
}
} else {
break;
}
}

Ok(())
}
}
54 changes: 33 additions & 21 deletions test/sys/test_timerfd.rs
@@ -1,49 +1,61 @@
use nix::sys::time::{TimeSpec, TimeValLike};
use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags};
use nix::unistd::read;
use std::os::unix::io::AsRawFd;
use std::time::Instant;

#[test]
pub fn test_timerfd_oneshot() {
let mut buffer = [0u8; 8];
let fd = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
.unwrap();
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();

let before = Instant::now();

fd.settime(
Expiration::OneShot(TimeSpec::seconds(1)),
TimerSetTimeFlags::empty(),
)
.unwrap();
timer
.set(
Expiration::OneShot(TimeSpec::seconds(1)),
TimerSetTimeFlags::empty(),
)
.unwrap();

read(fd.as_raw_fd(), &mut buffer).unwrap();
timer.wait().unwrap();

let millis = before.elapsed().as_millis();
assert!(millis > 900);
}

#[test]
pub fn test_timerfd_interval() {
let mut buffer = [0u8; 8];
let fd = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
.unwrap();
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();

let before = Instant::now();
fd.settime(
Expiration::IntervalDelayed(TimeSpec::seconds(1), TimeSpec::seconds(2)),
TimerSetTimeFlags::empty(),
)
.unwrap();
timer
.set(
Expiration::IntervalDelayed(TimeSpec::seconds(1), TimeSpec::seconds(2)),
TimerSetTimeFlags::empty(),
)
.unwrap();

read(fd.as_raw_fd(), &mut buffer).unwrap();
timer.wait().unwrap();

let start_delay = before.elapsed().as_millis();
assert!(start_delay > 900);

read(fd.as_raw_fd(), &mut buffer).unwrap();
timer.wait().unwrap();

let interval_delay = before.elapsed().as_millis();
assert!(interval_delay > 2900);
}

#[test]
pub fn test_timerfd_unset() {
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();

timer
.set(
Expiration::OneShot(TimeSpec::seconds(1)),
TimerSetTimeFlags::empty(),
)
.unwrap();

timer.unset().unwrap();

assert!(timer.get().unwrap() == None);
}

0 comments on commit 05cd7f1

Please sign in to comment.