1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //! This module provides a time hack to work around the broken `Instant` type in the standard
18 //! library.
19 //!
20 //! `BootTime` looks like `Instant`, but represents `CLOCK_BOOTTIME` instead of `CLOCK_MONOTONIC`.
21 //! This means the clock increments correctly during suspend.
22
23 pub use std::time::Duration;
24
25 use std::io;
26
27 use futures::future::pending;
28 use std::convert::TryInto;
29 use std::fmt;
30 use std::future::Future;
31 use std::os::unix::io::{AsRawFd, RawFd};
32 use tokio::io::unix::AsyncFd;
33 use tokio::select;
34
35 /// Represents a moment in time, with differences including time spent in suspend. Only valid for
36 /// a single boot - numbers from different boots are incomparable.
37 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38 pub struct BootTime {
39 d: Duration,
40 }
41
42 // Return an error with the same structure as tokio::time::timeout to facilitate migration off it,
43 // and hopefully some day back to it.
44 /// Error returned by timeout
45 #[derive(Debug, PartialEq)]
46 pub struct Elapsed(());
47
48 impl fmt::Display for Elapsed {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result49 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
50 "deadline has elapsed".fmt(fmt)
51 }
52 }
53
54 impl std::error::Error for Elapsed {}
55
56 impl BootTime {
57 /// Gets a `BootTime` representing the current moment in time.
now() -> BootTime58 pub fn now() -> BootTime {
59 let mut t = libc::timespec { tv_sec: 0, tv_nsec: 0 };
60 // # Safety
61 // clock_gettime's only action will be to possibly write to the pointer provided,
62 // and no borrows exist from that object other than the &mut used to construct the pointer
63 // itself.
64 if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut t as *mut libc::timespec) } != 0
65 {
66 panic!(
67 "libc::clock_gettime(libc::CLOCK_BOOTTIME) failed: {:?}",
68 io::Error::last_os_error()
69 );
70 }
71 BootTime { d: Duration::new(t.tv_sec as u64, t.tv_nsec as u32) }
72 }
73
74 /// Determines how long has elapsed since the provided `BootTime`.
elapsed(&self) -> Duration75 pub fn elapsed(&self) -> Duration {
76 BootTime::now().checked_duration_since(*self).unwrap()
77 }
78
79 /// Add a specified time delta to a moment in time. If this would overflow the representation,
80 /// returns `None`.
checked_add(&self, duration: Duration) -> Option<BootTime>81 pub fn checked_add(&self, duration: Duration) -> Option<BootTime> {
82 Some(BootTime { d: self.d.checked_add(duration)? })
83 }
84
85 /// Finds the difference from an earlier point in time. If the provided time is later, returns
86 /// `None`.
checked_duration_since(&self, earlier: BootTime) -> Option<Duration>87 pub fn checked_duration_since(&self, earlier: BootTime) -> Option<Duration> {
88 self.d.checked_sub(earlier.d)
89 }
90 }
91
92 struct TimerFd(RawFd);
93
94 impl Drop for TimerFd {
drop(&mut self)95 fn drop(&mut self) {
96 // # Safety
97 // The fd is owned by the TimerFd struct, and no memory access occurs as a result of this
98 // call.
99 unsafe {
100 libc::close(self.0);
101 }
102 }
103 }
104
105 impl AsRawFd for TimerFd {
as_raw_fd(&self) -> RawFd106 fn as_raw_fd(&self) -> RawFd {
107 self.0
108 }
109 }
110
111 impl TimerFd {
create() -> io::Result<Self>112 fn create() -> io::Result<Self> {
113 // # Unsafe
114 // This libc call will either give us back a file descriptor or fail, it does not act on
115 // memory or resources.
116 let raw = unsafe {
117 libc::timerfd_create(libc::CLOCK_BOOTTIME, libc::TFD_NONBLOCK | libc::TFD_CLOEXEC)
118 };
119 if raw < 0 {
120 return Err(io::Error::last_os_error());
121 }
122 Ok(Self(raw))
123 }
124
set(&self, duration: Duration)125 fn set(&self, duration: Duration) {
126 assert_ne!(duration, Duration::from_millis(0));
127 let timer = libc::itimerspec {
128 it_interval: libc::timespec { tv_sec: 0, tv_nsec: 0 },
129 it_value: libc::timespec {
130 tv_sec: duration.as_secs().try_into().unwrap(),
131 tv_nsec: duration.subsec_nanos().try_into().unwrap(),
132 },
133 };
134 // # Unsafe
135 // We own `timer` and there are no borrows to it other than the pointer we pass to
136 // timerfd_settime. timerfd_settime is explicitly documented to handle a null output
137 // parameter for its fourth argument by not filling out the output. The fd passed in at
138 // self.0 is owned by the `TimerFd` struct, so we aren't breaking anyone else's invariants.
139 if unsafe { libc::timerfd_settime(self.0, 0, &timer, std::ptr::null_mut()) } != 0 {
140 panic!("timerfd_settime failed: {:?}", io::Error::last_os_error());
141 }
142 }
143 }
144
145 /// Runs the provided future until completion or `duration` has passed on the `CLOCK_BOOTTIME`
146 /// clock. In the event of a timeout, returns the elapsed time as an error.
timeout<T>(duration: Duration, future: impl Future<Output = T>) -> Result<T, Elapsed>147 pub async fn timeout<T>(duration: Duration, future: impl Future<Output = T>) -> Result<T, Elapsed> {
148 // Ideally, all timeouts in a runtime would share a timerfd. That will be much more
149 // straightforwards to implement when moving this functionality into `tokio`.
150
151 // According to timerfd_settime(), setting zero duration will disarm the timer, so
152 // we return immediate timeout here.
153 // Can't use is_zero() for now because sc-mainline-prod's Rust version is below 1.53.
154 if duration == Duration::from_millis(0) {
155 return Err(Elapsed(()));
156 }
157
158 // The failure conditions for this are rare (see `man 2 timerfd_create`) and the caller would
159 // not be able to do much in response to them. When integrated into tokio, this would be called
160 // during runtime setup.
161 let timer_fd = TimerFd::create().unwrap();
162 timer_fd.set(duration);
163 let async_fd = AsyncFd::new(timer_fd).unwrap();
164 select! {
165 v = future => Ok(v),
166 _ = async_fd.readable() => Err(Elapsed(())),
167 }
168 }
169
170 /// Provides a future which will complete once the provided duration has passed, as measured by the
171 /// `CLOCK_BOOTTIME` clock.
sleep(duration: Duration)172 pub async fn sleep(duration: Duration) {
173 assert!(timeout(duration, pending::<()>()).await.is_err());
174 }
175
176 #[test]
monotonic_smoke()177 fn monotonic_smoke() {
178 for _ in 0..1000 {
179 // If BootTime is not monotonic, .elapsed() will panic on the unwrap.
180 BootTime::now().elapsed();
181 }
182 }
183
184 #[test]
round_trip()185 fn round_trip() {
186 use std::thread::sleep;
187 for _ in 0..10 {
188 let start = BootTime::now();
189 sleep(Duration::from_millis(1));
190 let end = BootTime::now();
191 let delta = end.checked_duration_since(start).unwrap();
192 assert_eq!(start.checked_add(delta).unwrap(), end);
193 }
194 }
195
196 #[tokio::test]
timeout_drift()197 async fn timeout_drift() {
198 let delta = Duration::from_millis(20);
199 for _ in 0..10 {
200 let start = BootTime::now();
201 assert!(timeout(delta, pending::<()>()).await.is_err());
202 let taken = start.elapsed();
203 let drift = if taken > delta { taken - delta } else { delta - taken };
204 assert!(drift < Duration::from_millis(5));
205 }
206
207 for _ in 0..10 {
208 let start = BootTime::now();
209 sleep(delta).await;
210 let taken = start.elapsed();
211 let drift = if taken > delta { taken - delta } else { delta - taken };
212 assert!(drift < Duration::from_millis(5));
213 }
214 }
215
216 #[tokio::test]
timeout_duration_zero()217 async fn timeout_duration_zero() {
218 let start = BootTime::now();
219 assert!(timeout(Duration::from_millis(0), pending::<()>()).await.is_err());
220 let taken = start.elapsed();
221 assert!(taken < Duration::from_millis(5));
222 }
223