From 05cd7f1598e292da041b534fb7e47043cf75200d Mon Sep 17 00:00:00 2001 From: Vincent Dagonneau Date: Mon, 29 Jun 2020 14:12:07 +0200 Subject: [PATCH] * Ran rustfmt. * 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. --- src/sys/time.rs | 5 ++ src/sys/timerfd.rs | 151 ++++++++++++++++++++++++++++++--------- test/sys/test_timerfd.rs | 54 ++++++++------ 3 files changed, 156 insertions(+), 54 deletions(-) diff --git a/src/sys/time.rs b/src/sys/time.rs index 51baa9e10f..973a3526c7 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -60,6 +60,11 @@ const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64; const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS; +impl From for TimeSpec { + fn from(ts: timespec) -> Self { + Self(ts) + } +} impl AsRef for TimeSpec { fn as_ref(&self) -> ×pec { diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs index 1921b94b5c..3086309e62 100644 --- a/src/sys/timerfd.rs +++ b/src/sys/timerfd.rs @@ -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}; @@ -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 for TimerSpec { fn as_ref(&self) -> &libc::itimerspec { &self.0 @@ -102,12 +117,10 @@ impl From 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(), @@ -116,9 +129,34 @@ impl From for TimerSpec { } } +impl From 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), @@ -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 { 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( @@ -184,4 +220,53 @@ impl TimerFd { }) .map(drop) } + + /// Get the parameters for the alarm currently set, if any. + pub fn get(&self) -> Result> { + 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(()) + } } diff --git a/test/sys/test_timerfd.rs b/test/sys/test_timerfd.rs index 03f051a6ef..24fb2ac002 100644 --- a/test/sys/test_timerfd.rs +++ b/test/sys/test_timerfd.rs @@ -1,24 +1,21 @@ 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); @@ -26,24 +23,39 @@ pub fn test_timerfd_oneshot() { #[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); +}