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