• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Waking timers for Bluetooth. Implemented using timerfd, but supposed to feel similar to
2 ///Tokio's time
3 use nix::sys::time::TimeSpec;
4 use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags};
5 use std::time::Duration;
6 use tokio::io::unix::AsyncFd;
7 
8 /// A single shot Alarm
9 pub struct Alarm {
10     fd: AsyncFd<TimerFd>,
11 }
12 
13 impl Alarm {
14     /// Construct a new alarm
new() -> Self15     pub fn new() -> Self {
16         let timer = TimerFd::new(get_clock(), TimerFlags::empty()).unwrap();
17         Self { fd: AsyncFd::new(timer).unwrap() }
18     }
19 
20     /// Reset the alarm to duration, starting from now
reset(&self, duration: Duration)21     pub fn reset(&self, duration: Duration) {
22         self.fd
23             .get_ref()
24             .set(Expiration::OneShot(TimeSpec::from(duration)), TimerSetTimeFlags::empty())
25             .unwrap();
26     }
27 
28     /// Completes when the alarm has expired
expired(&self)29     pub async fn expired(&self) {
30         let mut read_ready = self.fd.readable().await.unwrap();
31         read_ready.clear_ready();
32         drop(read_ready);
33         // Will not block, since we have confirmed it is readable
34         if self.fd.get_ref().get().unwrap().is_some() {
35             self.fd.get_ref().wait().unwrap();
36         }
37     }
38 }
39 
40 impl Default for Alarm {
default() -> Self41     fn default() -> Self {
42         Alarm::new()
43     }
44 }
45 
get_clock() -> ClockId46 fn get_clock() -> ClockId {
47     if cfg!(target_os = "android") {
48         ClockId::CLOCK_BOOTTIME_ALARM
49     } else {
50         ClockId::CLOCK_BOOTTIME
51     }
52 }
53 
54 #[cfg(test)]
55 mod tests {
56     use super::Alarm;
57     use std::time::Duration;
58 
59     #[test]
alarm_cancel_after_expired()60     fn alarm_cancel_after_expired() {
61         let runtime = tokio::runtime::Runtime::new().unwrap();
62         runtime.block_on(async {
63             let alarm = Alarm::new();
64             alarm.reset(Duration::from_millis(10));
65             tokio::time::sleep(Duration::from_millis(30)).await;
66             alarm.reset(Duration::from_millis(0));
67 
68             for _ in 0..10 {
69                 let ready_in_10_ms = async {
70                     tokio::time::sleep(Duration::from_millis(10)).await;
71                 };
72 
73                 tokio::select! {
74                     _ = alarm.expired() => (),
75                     _ = ready_in_10_ms => (),
76                 }
77             }
78         });
79     }
80 
81     #[test]
alarm_clear_ready_after_expired()82     fn alarm_clear_ready_after_expired() {
83         // After an alarm expired, we need to make sure we clear ready from AsyncFdReadyGuard.
84         // Otherwise it's still ready and select! won't work.
85         let runtime = tokio::runtime::Runtime::new().unwrap();
86         runtime.block_on(async {
87             let alarm = Alarm::new();
88             alarm.reset(Duration::from_millis(10));
89             alarm.expired().await;
90             let ready_in_10_ms = async {
91                 tokio::time::sleep(Duration::from_millis(10)).await;
92             };
93             tokio::select! {
94                 _ = alarm.expired() => (),
95                 _ = ready_in_10_ms => (),
96             }
97         });
98     }
99 }
100