Skip to content

Commit 3914a7b

Browse files
committed
where available use 64- or 128bit atomics instead of a Mutex to monotonize time
1 parent ae90dcf commit 3914a7b

File tree

3 files changed

+122
-12
lines changed

3 files changed

+122
-12
lines changed

library/std/src/time.rs

+2-10
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@
1212
1313
#![stable(feature = "time", since = "1.3.0")]
1414

15+
mod monotonic;
1516
#[cfg(test)]
1617
mod tests;
1718

18-
use crate::cmp;
1919
use crate::error::Error;
2020
use crate::fmt;
2121
use crate::ops::{Add, AddAssign, Sub, SubAssign};
2222
use crate::sys::time;
23-
use crate::sys_common::mutex::StaticMutex;
2423
use crate::sys_common::FromInner;
2524

2625
#[stable(feature = "time", since = "1.3.0")]
@@ -249,14 +248,7 @@ impl Instant {
249248
return Instant(os_now);
250249
}
251250

252-
static LOCK: StaticMutex = StaticMutex::new();
253-
static mut LAST_NOW: time::Instant = time::Instant::zero();
254-
unsafe {
255-
let _lock = LOCK.lock();
256-
let now = cmp::max(LAST_NOW, os_now);
257-
LAST_NOW = now;
258-
Instant(now)
259-
}
251+
Instant(monotonic::monotonize(os_now))
260252
}
261253

262254
/// Returns the amount of time elapsed from another instant to this one.

library/std/src/time/monotonic.rs

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use crate::sys::time;
2+
3+
#[inline]
4+
pub(super) fn monotonize(raw: time::Instant) -> time::Instant {
5+
inner::monotonize(raw)
6+
}
7+
8+
#[cfg(all(target_has_atomic = "64", not(target_has_atomic = "128")))]
9+
pub mod inner {
10+
use crate::sync::atomic::AtomicU64;
11+
use crate::sync::atomic::Ordering::*;
12+
use crate::sys::time;
13+
use crate::time::Duration;
14+
15+
const ZERO: time::Instant = time::Instant::zero();
16+
17+
// bits 30 and 31 are never used since the seconds part never exceeds 10^9
18+
const UNINITIALIZED: u64 = 0xff00_0000;
19+
static MONO: AtomicU64 = AtomicU64::new(UNINITIALIZED);
20+
21+
#[inline]
22+
pub(super) fn monotonize(raw: time::Instant) -> time::Instant {
23+
let delta = raw.checked_sub_instant(&ZERO).unwrap();
24+
let secs = delta.as_secs();
25+
// occupies no more than 30 bits (10^9 seconds)
26+
let nanos = delta.subsec_nanos() as u64;
27+
28+
// This wraps around every 136 years (2^32 seconds).
29+
// To detect backsliding we use wrapping arithmetic and declare forward steps smaller
30+
// than 2^31 seconds as expected and everything else as a backslide which will be
31+
// monotonized.
32+
// This could be a problem for programs that call instants at intervals greater
33+
// than 68 years. Interstellar probes may want to ensure that actually_monotonic() is true.
34+
let packed = (secs << 32) | nanos;
35+
let old = MONO.load(Relaxed);
36+
37+
if packed == UNINITIALIZED || packed.wrapping_sub(old) < u64::MAX / 2 {
38+
MONO.store(packed, Relaxed);
39+
raw
40+
} else {
41+
// Backslide occurred. We reconstruct monotonized time by assuming the clock will never
42+
// backslide more than 2`32 seconds which means we can reuse the upper 32bits from
43+
// the seconds.
44+
let secs = (secs & 0xffff_ffff << 32) | old >> 32;
45+
let nanos = old as u32;
46+
ZERO.checked_add_duration(&Duration::new(secs, nanos)).unwrap()
47+
}
48+
}
49+
}
50+
51+
#[cfg(target_has_atomic = "128")]
52+
pub mod inner {
53+
use crate::sync::atomic::AtomicU128;
54+
use crate::sync::atomic::Ordering::*;
55+
use crate::sys::time;
56+
use crate::time::Duration;
57+
58+
const ZERO: time::Instant = time::Instant::zero();
59+
static MONO: AtomicU128 = AtomicU128::new(0);
60+
61+
#[inline]
62+
pub(super) fn monotonize(raw: time::Instant) -> time::Instant {
63+
let delta = raw.checked_sub_instant(&ZERO).unwrap();
64+
// Split into seconds and nanos since Duration doesn't have a
65+
// constructor that takes an u128
66+
let secs = delta.as_secs() as u128;
67+
let nanos = delta.subsec_nanos() as u128;
68+
let timestamp: u128 = secs << 64 | nanos;
69+
let timestamp = MONO.fetch_max(timestamp, Relaxed).max(timestamp);
70+
let secs = (timestamp >> 64) as u64;
71+
let nanos = timestamp as u32;
72+
ZERO.checked_add_duration(&Duration::new(secs, nanos)).unwrap()
73+
}
74+
}
75+
76+
#[cfg(not(any(target_has_atomic = "64", target_has_atomic = "128")))]
77+
pub mod inner {
78+
use crate::cmp;
79+
use crate::sys::time;
80+
use crate::sys_common::mutex::StaticMutex;
81+
82+
#[inline]
83+
pub(super) fn monotonize(os_now: time::Instant) -> time::Instant {
84+
static LOCK: StaticMutex = StaticMutex::new();
85+
static mut LAST_NOW: time::Instant = time::Instant::zero();
86+
unsafe {
87+
let _lock = LOCK.lock();
88+
let now = cmp::max(LAST_NOW, os_now);
89+
LAST_NOW = now;
90+
now
91+
}
92+
}
93+
}

library/std/src/time/tests.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,33 @@ macro_rules! assert_almost_eq {
1313
#[test]
1414
fn instant_monotonic() {
1515
let a = Instant::now();
16-
let b = Instant::now();
17-
assert!(b >= a);
16+
loop {
17+
let b = Instant::now();
18+
assert!(b >= a);
19+
if b > a {
20+
break;
21+
}
22+
}
23+
}
24+
25+
#[test]
26+
fn instant_monotonic_concurrent() -> crate::thread::Result<()> {
27+
let threads: Vec<_> = (0..8)
28+
.map(|_| {
29+
crate::thread::spawn(|| {
30+
let mut old = Instant::now();
31+
for _ in 0..5_000_000 {
32+
let new = Instant::now();
33+
assert!(new >= old);
34+
old = new;
35+
}
36+
})
37+
})
38+
.collect();
39+
for t in threads {
40+
t.join()?;
41+
}
42+
Ok(())
1843
}
1944

2045
#[test]

0 commit comments

Comments
 (0)