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(&mut self, duration: Duration)21 pub fn reset(&mut self, duration: Duration) {
22 self.fd
23 .get_ref()
24 .set(Expiration::OneShot(TimeSpec::from(duration)), TimerSetTimeFlags::empty())
25 .unwrap();
26 }
27
28 /// Stop the alarm if it is currently started
cancel(&mut self)29 pub fn cancel(&mut self) {
30 self.reset(Duration::from_millis(0));
31 }
32
33 /// Completes when the alarm has expired
expired(&mut self)34 pub async fn expired(&mut self) {
35 let mut read_ready = self.fd.readable().await.unwrap();
36 read_ready.clear_ready();
37 drop(read_ready);
38 // Will not block, since we have confirmed it is readable
39 self.fd.get_ref().wait().unwrap();
40 }
41 }
42
43 impl Default for Alarm {
default() -> Self44 fn default() -> Self {
45 Alarm::new()
46 }
47 }
48
49 /// Similar to tokio's interval, except the first tick does *not* complete immediately
interval(period: Duration) -> Interval50 pub fn interval(period: Duration) -> Interval {
51 let timer = TimerFd::new(get_clock(), TimerFlags::empty()).unwrap();
52 timer.set(Expiration::Interval(TimeSpec::from(period)), TimerSetTimeFlags::empty()).unwrap();
53
54 Interval { fd: AsyncFd::new(timer).unwrap() }
55 }
56
57 /// Future returned by interval()
58 pub struct Interval {
59 fd: AsyncFd<TimerFd>,
60 }
61
62 impl Interval {
63 /// Call this to get the future for the next tick of the interval
tick(&mut self)64 pub async fn tick(&mut self) {
65 let mut read_ready = self.fd.readable().await.unwrap();
66 read_ready.clear_ready();
67 drop(read_ready);
68 // Will not block, since we have confirmed it is readable
69 self.fd.get_ref().wait().unwrap();
70 }
71 }
72
get_clock() -> ClockId73 fn get_clock() -> ClockId {
74 if cfg!(target_os = "android") {
75 ClockId::CLOCK_BOOTTIME_ALARM
76 } else {
77 ClockId::CLOCK_BOOTTIME
78 }
79 }
80
81 #[cfg(test)]
82 mod tests {
83 use super::interval;
84 use super::Alarm;
85 use crate::assert_near;
86 use std::time::{Duration, Instant};
87
88 #[test]
alarm_simple_case()89 fn alarm_simple_case() {
90 let runtime = tokio::runtime::Runtime::new().unwrap();
91 runtime.block_on(async {
92 let timer = Instant::now();
93 let mut alarm = Alarm::new();
94 alarm.reset(Duration::from_millis(10));
95 alarm.expired().await;
96
97 assert_near!(timer.elapsed().as_millis(), 10, 3);
98 });
99 }
100
101 #[test]
alarm_clear_ready_after_expired()102 fn alarm_clear_ready_after_expired() {
103 // After an alarm expired, we need to make sure we clear ready from AsyncFdReadyGuard.
104 // Otherwise it's still ready and select! won't work.
105 let runtime = tokio::runtime::Runtime::new().unwrap();
106 runtime.block_on(async {
107 let timer = Instant::now();
108 let mut alarm = Alarm::new();
109 alarm.reset(Duration::from_millis(10));
110 alarm.expired().await;
111 let ready_in_10_ms = async {
112 tokio::time::sleep(Duration::from_millis(10)).await;
113 };
114 tokio::select! {
115 _ = alarm.expired() => (),
116 _ = ready_in_10_ms => (),
117 }
118 assert_near!(timer.elapsed().as_millis(), 20, 3);
119 });
120 }
121
122 #[test]
interval_schedule_and_then_drop()123 fn interval_schedule_and_then_drop() {
124 let runtime = tokio::runtime::Runtime::new().unwrap();
125 runtime.block_on(async {
126 interval(Duration::from_millis(10));
127 });
128 }
129
130 #[test]
interval_simple_case()131 fn interval_simple_case() {
132 let runtime = tokio::runtime::Runtime::new().unwrap();
133 runtime.block_on(async {
134 let timer = Instant::now();
135 let mut interval = interval(Duration::from_millis(10));
136
137 for n in 1..10 {
138 interval.tick().await;
139 println!("{}", n);
140 assert_near!(timer.elapsed().as_millis(), 10 * n, 3);
141 }
142 });
143 }
144 }
145