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::os::fd::{AsFd, AsRawFd, RawFd};
6 use std::time::Duration;
7 use tokio::io::unix::AsyncFd;
8
9 /// A wrapper for `TimerFd` which implements `AsRawFd`.
10 #[derive(Debug)]
11 struct TimerFdWrapper(TimerFd);
12
13 impl TimerFdWrapper {
get(&self) -> nix::Result<Option<Expiration>>14 fn get(&self) -> nix::Result<Option<Expiration>> {
15 self.0.get()
16 }
17
set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> nix::Result<()>18 fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> nix::Result<()> {
19 self.0.set(expiration, flags)
20 }
21
wait(&self) -> nix::Result<()>22 fn wait(&self) -> nix::Result<()> {
23 self.0.wait()
24 }
25 }
26
27 impl AsRawFd for TimerFdWrapper {
as_raw_fd(&self) -> RawFd28 fn as_raw_fd(&self) -> RawFd {
29 self.0.as_fd().as_raw_fd()
30 }
31 }
32
33 /// A single shot Alarm
34 pub struct Alarm {
35 fd: AsyncFd<TimerFdWrapper>,
36 }
37
38 impl Alarm {
39 /// Construct a new alarm
new() -> Self40 pub fn new() -> Self {
41 let timer = TimerFd::new(get_clock(), TimerFlags::empty()).unwrap();
42 Self { fd: AsyncFd::new(TimerFdWrapper(timer)).unwrap() }
43 }
44
45 /// Reset the alarm to duration, starting from now
reset(&self, duration: Duration)46 pub fn reset(&self, duration: Duration) {
47 self.fd
48 .get_ref()
49 .set(Expiration::OneShot(TimeSpec::from(duration)), TimerSetTimeFlags::empty())
50 .unwrap();
51 }
52
53 /// Stop the alarm if it is currently started
cancel(&self)54 pub fn cancel(&self) {
55 self.reset(Duration::from_millis(0));
56 }
57
58 /// Completes when the alarm has expired
expired(&self)59 pub async fn expired(&self) {
60 let mut read_ready = self.fd.readable().await.unwrap();
61 read_ready.clear_ready();
62 drop(read_ready);
63 // Will not block, since we have confirmed it is readable
64 if self.fd.get_ref().get().unwrap().is_some() {
65 self.fd.get_ref().wait().unwrap();
66 }
67 }
68 }
69
70 impl Default for Alarm {
default() -> Self71 fn default() -> Self {
72 Alarm::new()
73 }
74 }
75
76 /// Similar to tokio's interval, except the first tick does *not* complete immediately
interval(period: Duration) -> Interval77 pub fn interval(period: Duration) -> Interval {
78 let timer = TimerFd::new(get_clock(), TimerFlags::empty()).unwrap();
79 timer.set(Expiration::Interval(TimeSpec::from(period)), TimerSetTimeFlags::empty()).unwrap();
80
81 Interval { fd: AsyncFd::new(TimerFdWrapper(timer)).unwrap() }
82 }
83
84 /// Future returned by interval()
85 pub struct Interval {
86 fd: AsyncFd<TimerFdWrapper>,
87 }
88
89 impl Interval {
90 /// Call this to get the future for the next tick of the interval
tick(&mut self)91 pub async fn tick(&mut self) {
92 let mut read_ready = self.fd.readable().await.unwrap();
93 read_ready.clear_ready();
94 drop(read_ready);
95 // Will not block, since we have confirmed it is readable
96 if self.fd.get_ref().get().unwrap().is_some() {
97 self.fd.get_ref().wait().unwrap();
98 }
99 }
100 }
101
get_clock() -> ClockId102 fn get_clock() -> ClockId {
103 if cfg!(target_os = "android") {
104 ClockId::CLOCK_BOOTTIME_ALARM
105 } else {
106 ClockId::CLOCK_BOOTTIME
107 }
108 }
109
110 #[cfg(test)]
111 mod tests {
112 use super::interval;
113 use super::Alarm;
114 use crate::assert_near;
115 use std::time::{Duration, Instant};
116
117 #[test]
alarm_cancel_after_expired()118 fn alarm_cancel_after_expired() {
119 let runtime = tokio::runtime::Runtime::new().unwrap();
120 runtime.block_on(async {
121 let alarm = Alarm::new();
122 alarm.reset(Duration::from_millis(10));
123 tokio::time::sleep(Duration::from_millis(30)).await;
124 alarm.cancel();
125
126 for _ in 0..10 {
127 let ready_in_10_ms = async {
128 tokio::time::sleep(Duration::from_millis(10)).await;
129 };
130
131 tokio::select! {
132 _ = alarm.expired() => (),
133 _ = ready_in_10_ms => (),
134 }
135 }
136 });
137 }
138
139 #[test]
alarm_clear_ready_after_expired()140 fn alarm_clear_ready_after_expired() {
141 // After an alarm expired, we need to make sure we clear ready from AsyncFdReadyGuard.
142 // Otherwise it's still ready and select! won't work.
143 let runtime = tokio::runtime::Runtime::new().unwrap();
144 runtime.block_on(async {
145 let timer = Instant::now();
146 let alarm = Alarm::new();
147 alarm.reset(Duration::from_millis(10));
148 alarm.expired().await;
149 let ready_in_10_ms = async {
150 tokio::time::sleep(Duration::from_millis(10)).await;
151 };
152 tokio::select! {
153 _ = alarm.expired() => (),
154 _ = ready_in_10_ms => (),
155 }
156 assert_near!(timer.elapsed().as_millis(), 20, 3);
157 });
158 }
159
160 #[test]
interval_schedule_and_then_drop()161 fn interval_schedule_and_then_drop() {
162 let runtime = tokio::runtime::Runtime::new().unwrap();
163 runtime.block_on(async {
164 interval(Duration::from_millis(10));
165 });
166 }
167 }
168