about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/time/monotonic.rs38
-rw-r--r--library/std/src/time/tests.rs18
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]