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