• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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