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 =
31 self.fd.readable().await.expect("TimerFd is never expected to fail to be readable");
32 read_ready.clear_ready();
33 drop(read_ready);
34 // Will not block, since we have confirmed it is readable
35 if self.fd.get_ref().get().unwrap().is_some() {
36 self.fd.get_ref().wait().unwrap();
37 }
38 }
39 }
40
41 impl Default for Alarm {
default() -> Self42 fn default() -> Self {
43 Alarm::new()
44 }
45 }
46
get_clock() -> ClockId47 fn get_clock() -> ClockId {
48 if cfg!(target_os = "android") {
49 ClockId::CLOCK_BOOTTIME_ALARM
50 } else {
51 ClockId::CLOCK_BOOTTIME
52 }
53 }
54
55 #[cfg(test)]
56 mod tests {
57 use super::Alarm;
58 use std::time::Duration;
59
60 #[test]
alarm_cancel_after_expired()61 fn alarm_cancel_after_expired() {
62 let runtime = tokio::runtime::Runtime::new().unwrap();
63 runtime.block_on(async {
64 let alarm = Alarm::new();
65 alarm.reset(Duration::from_millis(10));
66 tokio::time::sleep(Duration::from_millis(30)).await;
67 alarm.reset(Duration::from_millis(0));
68
69 for _ in 0..10 {
70 let ready_in_10_ms = async {
71 tokio::time::sleep(Duration::from_millis(10)).await;
72 };
73
74 tokio::select! {
75 _ = alarm.expired() => (),
76 _ = ready_in_10_ms => (),
77 }
78 }
79 });
80 }
81
82 #[test]
alarm_clear_ready_after_expired()83 fn alarm_clear_ready_after_expired() {
84 // After an alarm expired, we need to make sure we clear ready from AsyncFdReadyGuard.
85 // Otherwise it's still ready and select! won't work.
86 let runtime = tokio::runtime::Runtime::new().unwrap();
87 runtime.block_on(async {
88 let alarm = Alarm::new();
89 alarm.reset(Duration::from_millis(10));
90 alarm.expired().await;
91 let ready_in_10_ms = async {
92 tokio::time::sleep(Duration::from_millis(10)).await;
93 };
94 tokio::select! {
95 _ = alarm.expired() => (),
96 _ = ready_in_10_ms => (),
97 }
98 });
99 }
100 }
101