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, Eq)]
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: clock_gettime's only action will be to possibly write to the pointer provided,
61 // and no borrows exist from that object other than the &mut used to construct the pointer
62 // itself.
63 if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut t as *mut libc::timespec) } != 0
64 {
65 panic!(
66 "libc::clock_gettime(libc::CLOCK_BOOTTIME) failed: {:?}",
67 io::Error::last_os_error()
68 );
69 }
70 BootTime { d: Duration::new(t.tv_sec as u64, t.tv_nsec as u32) }
71 }
72
73 /// Determines how long has elapsed since the provided `BootTime`.
elapsed(&self) -> Duration74 pub fn elapsed(&self) -> Duration {
75 BootTime::now().checked_duration_since(*self).unwrap()
76 }
77
78 /// Add a specified time delta to a moment in time. If this would overflow the representation,
79 /// returns `None`.
checked_add(&self, duration: Duration) -> Option<BootTime>80 pub fn checked_add(&self, duration: Duration) -> Option<BootTime> {
81 Some(BootTime { d: self.d.checked_add(duration)? })
82 }
83
84 /// Finds the difference from an earlier point in time. If the provided time is later, returns
85 /// `None`.
checked_duration_since(&self, earlier: BootTime) -> Option<Duration>86 pub fn checked_duration_since(&self, earlier: BootTime) -> Option<Duration> {
87 self.d.checked_sub(earlier.d)
88 }
89 }
90
91 struct TimerFd(RawFd);
92
93 impl Drop for TimerFd {
drop(&mut self)94 fn drop(&mut self) {
95 // SAFETY: The fd is owned by the TimerFd struct, and no memory access occurs as a result of
96 // this call.
97 unsafe {
98 libc::close(self.0);
99 }
100 }
101 }
102
103 impl AsRawFd for TimerFd {
as_raw_fd(&self) -> RawFd104 fn as_raw_fd(&self) -> RawFd {
105 self.0
106 }
107 }
108
109 impl TimerFd {
create() -> io::Result<Self>110 fn create() -> io::Result<Self> {
111 // SAFETY: This libc call will either give us back a file descriptor or fail, it does not
112 // act on memory or resources.
113 let raw = unsafe {
114 libc::timerfd_create(libc::CLOCK_BOOTTIME, libc::TFD_NONBLOCK | libc::TFD_CLOEXEC)
115 };
116 if raw < 0 {
117 return Err(io::Error::last_os_error());
118 }
119 Ok(Self(raw))
120 }
121
set(&self, duration: Duration)122 fn set(&self, duration: Duration) {
123 assert_ne!(duration, Duration::from_millis(0));
124 let timer = libc::itimerspec {
125 it_interval: libc::timespec { tv_sec: 0, tv_nsec: 0 },
126 it_value: libc::timespec {
127 tv_sec: duration.as_secs().try_into().unwrap(),
128 tv_nsec: duration.subsec_nanos().try_into().unwrap(),
129 },
130 };
131 // SAFETY: We own `timer` and there are no borrows to it other than the pointer we pass to
132 // timerfd_settime. timerfd_settime is explicitly documented to handle a null output
133 // parameter for its fourth argument by not filling out the output. The fd passed in at
134 // self.0 is owned by the `TimerFd` struct, so we aren't breaking anyone else's invariants.
135 if unsafe { libc::timerfd_settime(self.0, 0, &timer, std::ptr::null_mut()) } != 0 {
136 panic!("timerfd_settime failed: {:?}", io::Error::last_os_error());
137 }
138 }
139 }
140
141 /// Runs the provided future until completion or `duration` has passed on the `CLOCK_BOOTTIME`
142 /// 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>143 pub async fn timeout<T>(duration: Duration, future: impl Future<Output = T>) -> Result<T, Elapsed> {
144 // Ideally, all timeouts in a runtime would share a timerfd. That will be much more
145 // straightforwards to implement when moving this functionality into `tokio`.
146
147 // According to timerfd_settime(), setting zero duration will disarm the timer, so
148 // we return immediate timeout here.
149 // Can't use is_zero() for now because sc-mainline-prod's Rust version is below 1.53.
150 if duration == Duration::from_millis(0) {
151 return Err(Elapsed(()));
152 }
153
154 // The failure conditions for this are rare (see `man 2 timerfd_create`) and the caller would
155 // not be able to do much in response to them. When integrated into tokio, this would be called
156 // during runtime setup.
157 let timer_fd = TimerFd::create().unwrap();
158 timer_fd.set(duration);
159 let async_fd = AsyncFd::new(timer_fd).unwrap();
160 select! {
161 v = future => Ok(v),
162 _ = async_fd.readable() => Err(Elapsed(())),
163 }
164 }
165
166 /// Provides a future which will complete once the provided duration has passed, as measured by the
167 /// `CLOCK_BOOTTIME` clock.
sleep(duration: Duration)168 pub async fn sleep(duration: Duration) {
169 assert!(timeout(duration, pending::<()>()).await.is_err());
170 }
171
172 #[test]
monotonic_smoke()173 fn monotonic_smoke() {
174 for _ in 0..1000 {
175 // If BootTime is not monotonic, .elapsed() will panic on the unwrap.
176 BootTime::now().elapsed();
177 }
178 }
179
180 #[test]
round_trip()181 fn round_trip() {
182 use std::thread::sleep;
183 for _ in 0..10 {
184 let start = BootTime::now();
185 sleep(Duration::from_millis(1));
186 let end = BootTime::now();
187 let delta = end.checked_duration_since(start).unwrap();
188 assert_eq!(start.checked_add(delta).unwrap(), end);
189 }
190 }
191
192 #[tokio::test]
timeout_drift()193 async fn timeout_drift() {
194 let delta = Duration::from_millis(40);
195 for _ in 0..5 {
196 let start = BootTime::now();
197 assert!(timeout(delta, pending::<()>()).await.is_err());
198 let taken = start.elapsed();
199 let drift = if taken > delta { taken - delta } else { delta - taken };
200 assert!(drift < Duration::from_millis(10));
201 }
202
203 for _ in 0..5 {
204 let start = BootTime::now();
205 sleep(delta).await;
206 let taken = start.elapsed();
207 let drift = if taken > delta { taken - delta } else { delta - taken };
208 assert!(drift < Duration::from_millis(10));
209 }
210 }
211
212 #[tokio::test]
timeout_duration_zero()213 async fn timeout_duration_zero() {
214 let start = BootTime::now();
215 assert!(timeout(Duration::from_millis(0), pending::<()>()).await.is_err());
216 let taken = start.elapsed();
217 assert!(taken < Duration::from_millis(5));
218 }
219