correct overflows in the backslide case, add test
This commit is contained in:
parent
7256a6a86d
commit
ff12ab2d99
2 changed files with 48 additions and 8 deletions
library/std/src/time
|
@ -12,14 +12,19 @@ pub mod inner {
|
||||||
use crate::sys::time;
|
use crate::sys::time;
|
||||||
use crate::time::Duration;
|
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
|
// 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);
|
static MONO: AtomicU64 = AtomicU64::new(UNINITIALIZED);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn monotonize(raw: time::Instant) -> time::Instant {
|
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 delta = raw.checked_sub_instant(&ZERO).unwrap();
|
||||||
let secs = delta.as_secs();
|
let secs = delta.as_secs();
|
||||||
// occupies no more than 30 bits (10^9 seconds)
|
// 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
|
// 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.
|
// than 68 years. Interstellar probes may want to ensure that actually_monotonic() is true.
|
||||||
let packed = (secs << 32) | nanos;
|
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 {
|
if old == UNINITIALIZED || packed.wrapping_sub(old) < u64::MAX / 2 {
|
||||||
MONO.store(packed, Relaxed);
|
mono.store(packed, Relaxed);
|
||||||
raw
|
raw
|
||||||
} else {
|
} else {
|
||||||
// Backslide occurred. We reconstruct monotonized time by assuming the clock will never
|
// Backslide occurred. We reconstruct monotonized time from the upper 32 bit of the
|
||||||
// backslide more than 2`32 seconds which means we can reuse the upper 32bits from
|
// passed in value and the 64bits loaded from the atomic
|
||||||
// the seconds.
|
let seconds_lower = old >> 32;
|
||||||
let secs = (secs & 0xffff_ffff_0000_0000) | 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;
|
let nanos = old as u32;
|
||||||
ZERO.checked_add_duration(&Duration::new(secs, nanos)).unwrap()
|
ZERO.checked_add_duration(&Duration::new(secs, nanos)).unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
use super::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
use core::sync::atomic::AtomicU64;
|
||||||
use test::{black_box, Bencher};
|
use test::{black_box, Bencher};
|
||||||
|
|
||||||
macro_rules! assert_almost_eq {
|
macro_rules! assert_almost_eq {
|
||||||
|
@ -190,6 +191,23 @@ fn since_epoch() {
|
||||||
assert!(a < hundred_twenty_years);
|
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 {
|
macro_rules! bench_instant_threaded {
|
||||||
($bench_name:ident, $thread_count:expr) => {
|
($bench_name:ident, $thread_count:expr) => {
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
Loading…
Add table
Reference in a new issue