From ff12ab2d9913ff20db62379378ccddc6bffa9f4e Mon Sep 17 00:00:00 2001 From: The8472 Date: Mon, 16 Aug 2021 22:15:52 +0200 Subject: [PATCH] correct overflows in the backslide case, add test --- library/std/src/time/monotonic.rs | 38 ++++++++++++++++++++++++------- library/std/src/time/tests.rs | 18 +++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/library/std/src/time/monotonic.rs b/library/std/src/time/monotonic.rs index d4572ee76be..c347bc3d342 100644 --- a/library/std/src/time/monotonic.rs +++ b/library/std/src/time/monotonic.rs @@ -12,14 +12,19 @@ pub mod inner { use crate::sys::time; use crate::time::Duration; - const ZERO: time::Instant = time::Instant::zero(); + pub(in crate::time) const ZERO: time::Instant = time::Instant::zero(); // bits 30 and 31 are never used since the seconds part never exceeds 10^9 - const UNINITIALIZED: u64 = 0xff00_0000; + const UNINITIALIZED: u64 = 0b11 << 30; static MONO: AtomicU64 = AtomicU64::new(UNINITIALIZED); #[inline] pub(super) fn monotonize(raw: time::Instant) -> time::Instant { + monotonize_impl(&MONO, raw) + } + + #[inline] + pub(in crate::time) fn monotonize_impl(mono: &AtomicU64, raw: time::Instant) -> time::Instant { let delta = raw.checked_sub_instant(&ZERO).unwrap(); let secs = delta.as_secs(); // occupies no more than 30 bits (10^9 seconds) @@ -32,16 +37,33 @@ pub mod inner { // This could be a problem for programs that call instants at intervals greater // than 68 years. Interstellar probes may want to ensure that actually_monotonic() is true. let packed = (secs << 32) | nanos; - let old = MONO.load(Relaxed); + let old = mono.load(Relaxed); if old == UNINITIALIZED || packed.wrapping_sub(old) < u64::MAX / 2 { - MONO.store(packed, Relaxed); + mono.store(packed, Relaxed); raw } else { - // Backslide occurred. We reconstruct monotonized time by assuming the clock will never - // backslide more than 2`32 seconds which means we can reuse the upper 32bits from - // the seconds. - let secs = (secs & 0xffff_ffff_0000_0000) | old >> 32; + // Backslide occurred. We reconstruct monotonized time from the upper 32 bit of the + // passed in value and the 64bits loaded from the atomic + let seconds_lower = old >> 32; + let mut seconds_upper = secs & 0xffff_ffff_0000_0000; + if secs & 0xffff_ffff > seconds_lower { + // Backslide caused the lower 32bit of the seconds part to wrap. + // This must be the case because the seconds part is larger even though + // we are in the backslide branch, i.e. the seconds count should be smaller or equal. + // + // We assume that backslides are smaller than 2^32 seconds + // which means we need to add 1 to the upper half to restore it. + // + // Example: + // most recent observed time: 0xA1_0000_0000_0000_0000u128 + // bits stored in AtomicU64: 0x0000_0000_0000_0000u64 + // backslide by 1s + // caller time is 0xA0_ffff_ffff_0000_0000u128 + // -> we can fix up the upper half time by adding 1 << 32 + seconds_upper = seconds_upper.wrapping_add(0x1_0000_0000); + } + let secs = seconds_upper | seconds_lower; let nanos = old as u32; ZERO.checked_add_duration(&Duration::new(secs, nanos)).unwrap() } diff --git a/library/std/src/time/tests.rs b/library/std/src/time/tests.rs index 3c578ed5051..82693d35e15 100644 --- a/library/std/src/time/tests.rs +++ b/library/std/src/time/tests.rs @@ -1,4 +1,5 @@ use super::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use core::sync::atomic::AtomicU64; use test::{black_box, Bencher}; macro_rules! assert_almost_eq { @@ -190,6 +191,23 @@ fn since_epoch() { assert!(a < hundred_twenty_years); } +#[cfg(all(target_has_atomic = "64", not(target_has_atomic = "128")))] +#[test] +fn monotonizer_wrapping_backslide() { + use super::monotonic::inner::{monotonize_impl, ZERO}; + + let reference = AtomicU64::new(0); + + let time = ZERO.checked_add_duration(&Duration::from_secs(0xffff_ffff)).unwrap(); + + let monotonized = monotonize_impl(&reference, time); + let expected = ZERO.checked_add_duration(&Duration::from_secs(1 << 32)).unwrap(); + assert_eq!( + monotonized, expected, + "64bit monotonizer should handle overflows in the seconds part" + ); +} + macro_rules! bench_instant_threaded { ($bench_name:ident, $thread_count:expr) => { #[bench]