Skip to content

Commit

Permalink
Removed the public status of TimerSpec, as it should not be exposed…
Browse files Browse the repository at this point in the history
… to the user.

Implemented `FromRawFd` for `TimerFd` as it already implements `AsRawFd`.

Addressed comments from the latest code review:
  - Removed upper bound assertions on timer expirations in tests.
  - Made the main example runnable and added code to show how to wait for the timer.
  - Refactored `ClockId` to use `libc_enum`.
  - Added comments for all public parts of the module.
  - Wrapped to 80 cols.
  - Changed the size of the buffer in the tests to the minimum required.
  • Loading branch information
vdagonneau committed Jun 25, 2020
1 parent cd70cf3 commit 4a3f5df
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 42 deletions.
125 changes: 94 additions & 31 deletions src/sys/timerfd.rs
@@ -1,33 +1,45 @@
//! Timer API via file descriptors.
//!
//! Timer FD is a Linux-only API to create timers and get expiration notifications through file
//! descriptors.
//! Timer FD is a Linux-only API to create timers and get expiration
//! notifications through file descriptors.
//!
//! For more documentation, please read [timerfd_create(2)](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
//!
//! # Examples
//!
//! Create a new one-shot timer that expires after 5 seconds.
//! ```no_run
//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags, Expiration};
//! Create a new one-shot timer that expires after 1 second.
//! ```
//! # use std::os::unix::io::AsRawFd;
//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags,
//! # Expiration};
//! # use nix::sys::time::{TimeSpec, TimeValLike};
//! # use nix::unistd::read;
//! #
//! // We create a new monotonic timer.
//! let fd = TimerFd::new(ClockId::Monotonic, TimerFlags::empty()).unwrap();
//! let fd = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
//! .unwrap();
//!
//! // We set a new one-shot timer in 5 seconds.
//! fd.settime(TimerSetTimeFlags::empty(), Expiration::OneShot(TimeSpec::seconds(5))).unwrap();
//! fd.settime(
//! Expiration::OneShot(TimeSpec::seconds(5)),
//! TimerSetTimeFlags::empty()
//! ).unwrap();
//!
//! // We wait for the timer to expire.
//! read(fd.as_raw_fd(), &mut [0u8; 8]).unwrap();
//! ```
use crate::errno::Errno;
use crate::sys::time::TimeSpec;
use crate::Result;
use bitflags::bitflags;
use libc::c_int;
use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};

/// A timerfd instance. This is also a file descriptor, you can feed it to
/// other interfaces consuming file descriptors, epoll for example.
#[derive(Debug, Clone, Copy)]
pub struct TimerFd {
fd: i32,
fd: RawFd,
}

impl AsRawFd for TimerFd {
Expand All @@ -36,30 +48,43 @@ impl AsRawFd for TimerFd {
}
}

#[derive(Debug, Clone, Copy)]
pub enum ClockId {
Realtime,
Monotonic,
BootTime,
RealtimeAlarm,
BootTimeAlarm,
impl FromRawFd for TimerFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
TimerFd { fd }
}
}

libc_enum! {
/// The type of the clock used to mark the progress of the timer. For more
/// details on each kind of clock, please refer to [timerfd_create(2)](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
#[repr(i32)]
pub enum ClockId {
CLOCK_REALTIME,
CLOCK_MONOTONIC,
CLOCK_BOOTTIME,
CLOCK_REALTIME_ALARM,
CLOCK_BOOTTIME_ALARM,
}
}

libc_bitflags! {
/// Additional flags to change the behaviour of the file descriptor at the
/// time of creation.
pub struct TimerFlags: c_int {
TFD_NONBLOCK;
TFD_CLOEXEC;
}
}

bitflags! {
/// Flags that are used for arming the timer.
pub struct TimerSetTimeFlags: libc::c_int {
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
}
}

#[derive(Debug, Clone, Copy)]
pub struct TimerSpec(libc::itimerspec);
struct TimerSpec(libc::itimerspec);

impl AsRef<libc::itimerspec> for TimerSpec {
fn as_ref(&self) -> &libc::itimerspec {
Expand All @@ -77,10 +102,12 @@ 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 @@ -89,6 +116,8 @@ impl From<Expiration> for TimerSpec {
}
}

/// An enumeration allowing the definition of the expiration time of an alarm,
/// recurring or not.
#[derive(Debug, Clone, Copy)]
pub enum Expiration {
OneShot(TimeSpec),
Expand All @@ -97,19 +126,53 @@ 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).
pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> {
let clid = match clockid {
ClockId::Realtime => libc::CLOCK_REALTIME,
ClockId::Monotonic => libc::CLOCK_MONOTONIC,
ClockId::BootTime => libc::CLOCK_BOOTTIME,
ClockId::RealtimeAlarm => libc::CLOCK_REALTIME_ALARM,
ClockId::BootTimeAlarm => libc::CLOCK_BOOTTIME_ALARM,
};

Errno::result(unsafe { libc::timerfd_create(clid, flags.bits()) }).map(|fd| Self { fd })
Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) })
.map(|fd| Self { fd })
}

pub fn settime(&self, flags: TimerSetTimeFlags, expiration: Expiration) -> Result<()> {
/// 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
/// 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 settime(&self, expiration: Expiration, flags: TimerSetTimeFlags)
-> Result<()>
{
let timerspec: TimerSpec = expiration.into();
Errno::result(unsafe {
libc::timerfd_settime(
Expand Down
22 changes: 11 additions & 11 deletions test/sys/test_timerfd.rs
@@ -1,49 +1,49 @@
use nix::errno::Errno;
use nix::sys::time::{TimeSpec, TimeValLike};
use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags};
use nix::unistd::read;
use nix::Error;
use std::os::unix::io::AsRawFd;
use std::time::Instant;

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

let before = Instant::now();

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

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

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

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

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

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

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

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

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

0 comments on commit 4a3f5df

Please sign in to comment.