223 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
| /*
 | |
|  * Copyright (C) 2021 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| //! This module provides a time hack to work around the broken `Instant` type in the standard
 | |
| //! library.
 | |
| //!
 | |
| //! `BootTime` looks like `Instant`, but represents `CLOCK_BOOTTIME` instead of `CLOCK_MONOTONIC`.
 | |
| //! This means the clock increments correctly during suspend.
 | |
| 
 | |
| pub use std::time::Duration;
 | |
| 
 | |
| use std::io;
 | |
| 
 | |
| use futures::future::pending;
 | |
| use std::convert::TryInto;
 | |
| use std::fmt;
 | |
| use std::future::Future;
 | |
| use std::os::unix::io::{AsRawFd, RawFd};
 | |
| use tokio::io::unix::AsyncFd;
 | |
| use tokio::select;
 | |
| 
 | |
| /// Represents a moment in time, with differences including time spent in suspend. Only valid for
 | |
| /// a single boot - numbers from different boots are incomparable.
 | |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | |
| pub struct BootTime {
 | |
|     d: Duration,
 | |
| }
 | |
| 
 | |
| // Return an error with the same structure as tokio::time::timeout to facilitate migration off it,
 | |
| // and hopefully some day back to it.
 | |
| /// Error returned by timeout
 | |
| #[derive(Debug, PartialEq)]
 | |
| pub struct Elapsed(());
 | |
| 
 | |
| impl fmt::Display for Elapsed {
 | |
|     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
 | |
|         "deadline has elapsed".fmt(fmt)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl std::error::Error for Elapsed {}
 | |
| 
 | |
| impl BootTime {
 | |
|     /// Gets a `BootTime` representing the current moment in time.
 | |
|     pub fn now() -> BootTime {
 | |
|         let mut t = libc::timespec { tv_sec: 0, tv_nsec: 0 };
 | |
|         // # Safety
 | |
|         // clock_gettime's only action will be to possibly write to the pointer provided,
 | |
|         // and no borrows exist from that object other than the &mut used to construct the pointer
 | |
|         // itself.
 | |
|         if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut t as *mut libc::timespec) } != 0
 | |
|         {
 | |
|             panic!(
 | |
|                 "libc::clock_gettime(libc::CLOCK_BOOTTIME) failed: {:?}",
 | |
|                 io::Error::last_os_error()
 | |
|             );
 | |
|         }
 | |
|         BootTime { d: Duration::new(t.tv_sec as u64, t.tv_nsec as u32) }
 | |
|     }
 | |
| 
 | |
|     /// Determines how long has elapsed since the provided `BootTime`.
 | |
|     pub fn elapsed(&self) -> Duration {
 | |
|         BootTime::now().checked_duration_since(*self).unwrap()
 | |
|     }
 | |
| 
 | |
|     /// Add a specified time delta to a moment in time. If this would overflow the representation,
 | |
|     /// returns `None`.
 | |
|     pub fn checked_add(&self, duration: Duration) -> Option<BootTime> {
 | |
|         Some(BootTime { d: self.d.checked_add(duration)? })
 | |
|     }
 | |
| 
 | |
|     /// Finds the difference from an earlier point in time. If the provided time is later, returns
 | |
|     /// `None`.
 | |
|     pub fn checked_duration_since(&self, earlier: BootTime) -> Option<Duration> {
 | |
|         self.d.checked_sub(earlier.d)
 | |
|     }
 | |
| }
 | |
| 
 | |
| struct TimerFd(RawFd);
 | |
| 
 | |
| impl Drop for TimerFd {
 | |
|     fn drop(&mut self) {
 | |
|         // # Safety
 | |
|         // The fd is owned by the TimerFd struct, and no memory access occurs as a result of this
 | |
|         // call.
 | |
|         unsafe {
 | |
|             libc::close(self.0);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl AsRawFd for TimerFd {
 | |
|     fn as_raw_fd(&self) -> RawFd {
 | |
|         self.0
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl TimerFd {
 | |
|     fn create() -> io::Result<Self> {
 | |
|         // # Unsafe
 | |
|         // This libc call will either give us back a file descriptor or fail, it does not act on
 | |
|         // memory or resources.
 | |
|         let raw = unsafe {
 | |
|             libc::timerfd_create(libc::CLOCK_BOOTTIME, libc::TFD_NONBLOCK | libc::TFD_CLOEXEC)
 | |
|         };
 | |
|         if raw < 0 {
 | |
|             return Err(io::Error::last_os_error());
 | |
|         }
 | |
|         Ok(Self(raw))
 | |
|     }
 | |
| 
 | |
|     fn set(&self, duration: Duration) {
 | |
|         assert_ne!(duration, Duration::from_millis(0));
 | |
|         let timer = libc::itimerspec {
 | |
|             it_interval: libc::timespec { tv_sec: 0, tv_nsec: 0 },
 | |
|             it_value: libc::timespec {
 | |
|                 tv_sec: duration.as_secs().try_into().unwrap(),
 | |
|                 tv_nsec: duration.subsec_nanos().try_into().unwrap(),
 | |
|             },
 | |
|         };
 | |
|         // # Unsafe
 | |
|         // We own `timer` and there are no borrows to it other than the pointer we pass to
 | |
|         // timerfd_settime. timerfd_settime is explicitly documented to handle a null output
 | |
|         // parameter for its fourth argument by not filling out the output. The fd passed in at
 | |
|         // self.0 is owned by the `TimerFd` struct, so we aren't breaking anyone else's invariants.
 | |
|         if unsafe { libc::timerfd_settime(self.0, 0, &timer, std::ptr::null_mut()) } != 0 {
 | |
|             panic!("timerfd_settime failed: {:?}", io::Error::last_os_error());
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Runs the provided future until completion or `duration` has passed on the `CLOCK_BOOTTIME`
 | |
| /// clock. In the event of a timeout, returns the elapsed time as an error.
 | |
| pub async fn timeout<T>(duration: Duration, future: impl Future<Output = T>) -> Result<T, Elapsed> {
 | |
|     // Ideally, all timeouts in a runtime would share a timerfd. That will be much more
 | |
|     // straightforwards to implement when moving this functionality into `tokio`.
 | |
| 
 | |
|     // According to timerfd_settime(), setting zero duration will disarm the timer, so
 | |
|     // we return immediate timeout here.
 | |
|     // Can't use is_zero() for now because sc-mainline-prod's Rust version is below 1.53.
 | |
|     if duration == Duration::from_millis(0) {
 | |
|         return Err(Elapsed(()));
 | |
|     }
 | |
| 
 | |
|     // The failure conditions for this are rare (see `man 2 timerfd_create`) and the caller would
 | |
|     // not be able to do much in response to them. When integrated into tokio, this would be called
 | |
|     // during runtime setup.
 | |
|     let timer_fd = TimerFd::create().unwrap();
 | |
|     timer_fd.set(duration);
 | |
|     let async_fd = AsyncFd::new(timer_fd).unwrap();
 | |
|     select! {
 | |
|         v = future => Ok(v),
 | |
|         _ = async_fd.readable() => Err(Elapsed(())),
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Provides a future which will complete once the provided duration has passed, as measured by the
 | |
| /// `CLOCK_BOOTTIME` clock.
 | |
| pub async fn sleep(duration: Duration) {
 | |
|     assert!(timeout(duration, pending::<()>()).await.is_err());
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn monotonic_smoke() {
 | |
|     for _ in 0..1000 {
 | |
|         // If BootTime is not monotonic, .elapsed() will panic on the unwrap.
 | |
|         BootTime::now().elapsed();
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn round_trip() {
 | |
|     use std::thread::sleep;
 | |
|     for _ in 0..10 {
 | |
|         let start = BootTime::now();
 | |
|         sleep(Duration::from_millis(1));
 | |
|         let end = BootTime::now();
 | |
|         let delta = end.checked_duration_since(start).unwrap();
 | |
|         assert_eq!(start.checked_add(delta).unwrap(), end);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn timeout_drift() {
 | |
|     let delta = Duration::from_millis(20);
 | |
|     for _ in 0..10 {
 | |
|         let start = BootTime::now();
 | |
|         assert!(timeout(delta, pending::<()>()).await.is_err());
 | |
|         let taken = start.elapsed();
 | |
|         let drift = if taken > delta { taken - delta } else { delta - taken };
 | |
|         assert!(drift < Duration::from_millis(5));
 | |
|     }
 | |
| 
 | |
|     for _ in 0..10 {
 | |
|         let start = BootTime::now();
 | |
|         sleep(delta).await;
 | |
|         let taken = start.elapsed();
 | |
|         let drift = if taken > delta { taken - delta } else { delta - taken };
 | |
|         assert!(drift < Duration::from_millis(5));
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn timeout_duration_zero() {
 | |
|     let start = BootTime::now();
 | |
|     assert!(timeout(Duration::from_millis(0), pending::<()>()).await.is_err());
 | |
|     let taken = start.elapsed();
 | |
|     assert!(taken < Duration::from_millis(5));
 | |
| }
 |