//! Waking timers for Bluetooth. Implemented using timerfd, but supposed to feel similar to ///Tokio's time use nix::sys::time::TimeSpec; use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags}; use std::os::fd::{AsFd, AsRawFd, RawFd}; use std::time::Duration; use tokio::io::unix::AsyncFd; /// A wrapper for `TimerFd` which implements `AsRawFd`. #[derive(Debug)] struct TimerFdWrapper(TimerFd); impl TimerFdWrapper { fn get(&self) -> nix::Result<Option<Expiration>> { self.0.get() } fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> nix::Result<()> { self.0.set(expiration, flags) } fn wait(&self) -> nix::Result<()> { self.0.wait() } } impl AsRawFd for TimerFdWrapper { fn as_raw_fd(&self) -> RawFd { self.0.as_fd().as_raw_fd() } } /// A single shot Alarm pub struct Alarm { fd: AsyncFd<TimerFdWrapper>, } impl Alarm { /// Construct a new alarm pub fn new() -> Self { let timer = TimerFd::new(get_clock(), TimerFlags::empty()).unwrap(); Self { fd: AsyncFd::new(TimerFdWrapper(timer)).unwrap() } } /// Reset the alarm to duration, starting from now pub fn reset(&self, duration: Duration) { self.fd .get_ref() .set(Expiration::OneShot(TimeSpec::from(duration)), TimerSetTimeFlags::empty()) .unwrap(); } /// Stop the alarm if it is currently started pub fn cancel(&self) { self.reset(Duration::from_millis(0)); } /// Completes when the alarm has expired pub async fn expired(&self) { let mut read_ready = self.fd.readable().await.unwrap(); read_ready.clear_ready(); drop(read_ready); // Will not block, since we have confirmed it is readable if self.fd.get_ref().get().unwrap().is_some() { self.fd.get_ref().wait().unwrap(); } } } impl Default for Alarm { fn default() -> Self { Alarm::new() } } /// Similar to tokio's interval, except the first tick does *not* complete immediately pub fn interval(period: Duration) -> Interval { let timer = TimerFd::new(get_clock(), TimerFlags::empty()).unwrap(); timer.set(Expiration::Interval(TimeSpec::from(period)), TimerSetTimeFlags::empty()).unwrap(); Interval { fd: AsyncFd::new(TimerFdWrapper(timer)).unwrap() } } /// Future returned by interval() pub struct Interval { fd: AsyncFd<TimerFdWrapper>, } impl Interval { /// Call this to get the future for the next tick of the interval pub async fn tick(&mut self) { let mut read_ready = self.fd.readable().await.unwrap(); read_ready.clear_ready(); drop(read_ready); // Will not block, since we have confirmed it is readable if self.fd.get_ref().get().unwrap().is_some() { self.fd.get_ref().wait().unwrap(); } } } fn get_clock() -> ClockId { if cfg!(target_os = "android") { ClockId::CLOCK_BOOTTIME_ALARM } else { ClockId::CLOCK_BOOTTIME } } #[cfg(test)] mod tests { use super::interval; use super::Alarm; use crate::assert_near; use std::time::{Duration, Instant}; #[test] fn alarm_cancel_after_expired() { let runtime = tokio::runtime::Runtime::new().unwrap(); runtime.block_on(async { let alarm = Alarm::new(); alarm.reset(Duration::from_millis(10)); tokio::time::sleep(Duration::from_millis(30)).await; alarm.cancel(); for _ in 0..10 { let ready_in_10_ms = async { tokio::time::sleep(Duration::from_millis(10)).await; }; tokio::select! { _ = alarm.expired() => (), _ = ready_in_10_ms => (), } } }); } #[test] fn alarm_clear_ready_after_expired() { // After an alarm expired, we need to make sure we clear ready from AsyncFdReadyGuard. // Otherwise it's still ready and select! won't work. let runtime = tokio::runtime::Runtime::new().unwrap(); runtime.block_on(async { let timer = Instant::now(); let alarm = Alarm::new(); alarm.reset(Duration::from_millis(10)); alarm.expired().await; let ready_in_10_ms = async { tokio::time::sleep(Duration::from_millis(10)).await; }; tokio::select! { _ = alarm.expired() => (), _ = ready_in_10_ms => (), } assert_near!(timer.elapsed().as_millis(), 20, 3); }); } #[test] fn interval_schedule_and_then_drop() { let runtime = tokio::runtime::Runtime::new().unwrap(); runtime.block_on(async { interval(Duration::from_millis(10)); }); } }