atomic_time/lib.rs
1//! Atomic time types.
2//!
3//! All types are thread-safe and use `portable_atomic::AtomicU128` under the
4//! hood. Operations are lock-free on platforms with native `AtomicU128`
5//! support; otherwise, `portable-atomic` may fall back to a global lock. Each
6//! type exposes an `is_lock_free` method to query the behavior of the current
7//! target.
8//!
9//! The `std` feature controls the `SystemTime` and `Instant` types. Disable it
10//! for `no_std` builds to use the duration types without the standard library.
11//!
12//! The optional `arbitrary`, `quickcheck`, and `proptest` features provide
13//! fuzzing and property-testing support for the duration and system-time
14//! atomic types. All three require `std`: the current upstream `arbitrary` v1
15//! crate itself requires `std`. `quickcheck` additionally implements [`Clone`]
16//! as a `SeqCst` snapshot: when another thread writes concurrently, the clone
17//! is the value observed at its atomic linearization point.
18//!
19//! [`AtomicInstant`] and [`AtomicOptionInstant`] intentionally do not implement
20//! these generation traits. Their values use a process-local baseline and
21//! cannot be generated from a deterministic, portable cross-run corpus.
22#![cfg_attr(not(feature = "std"), no_std)]
23#![cfg_attr(docsrs, feature(doc_cfg))]
24#![cfg_attr(docsrs, allow(unused_attributes))]
25#![deny(missing_docs)]
26#![forbid(unsafe_code)]
27
28pub use core::sync::atomic::Ordering;
29
30use portable_atomic::AtomicU128;
31
32mod duration;
33pub use duration::AtomicDuration;
34mod option_duration;
35pub use option_duration::AtomicOptionDuration;
36
37/// Utility functions for encoding and decoding [`core::time::Duration`] values.
38pub mod utils {
39 #[cfg(feature = "std")]
40 use std::time::{Duration, Instant, SystemTime};
41
42 #[cfg(feature = "std")]
43 fn init() -> (Duration, Instant) {
44 static ONCE: std::sync::OnceLock<(Duration, Instant)> = std::sync::OnceLock::new();
45
46 *ONCE.get_or_init(|| {
47 let epoch_dur = SystemTime::now()
48 .duration_since(SystemTime::UNIX_EPOCH)
49 .unwrap();
50 let instant_now = Instant::now();
51 (epoch_dur, instant_now)
52 })
53 }
54
55 /// Encodes an [`Instant`] into a [`Duration`] using a process-local baseline.
56 ///
57 /// The mapping is exact for instants in the same process that are representable
58 /// relative to that baseline. The baseline pairs a wall-clock duration with a
59 /// monotonic instant once; wall-clock adjustments made afterwards do not change
60 /// the mapping. Whether time advances while the machine sleeps inherits the
61 /// platform behavior of [`Instant`].
62 ///
63 /// This encoding is not a durable deadline format. Values decoded in another
64 /// process or after a restart are only wall-clock approximations.
65 ///
66 /// # Panics
67 ///
68 /// Panics if `instant` cannot be represented as a duration relative to the
69 /// process-local baseline.
70 #[cfg(feature = "std")]
71 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
72 #[inline(always)]
73 pub fn encode_instant_to_duration(instant: Instant) -> Duration {
74 let (epoch_dur, instant_now) = init();
75 if instant <= instant_now {
76 epoch_dur - (instant_now - instant)
77 } else {
78 epoch_dur + (instant - instant_now)
79 }
80 }
81
82 /// Decodes an [`Instant`] from a [`Duration`] using the process-local baseline.
83 ///
84 /// The mapping is exact for values encoded by this process within the platform's
85 /// representable `Instant` range. Values outside that range fall back to the
86 /// baseline instant; this is a fallback, not saturation. The behavior during
87 /// system sleep inherits the platform behavior of [`Instant`]. Encodings from
88 /// another process or a previous run are only wall-clock approximations and are
89 /// not durable deadlines.
90 #[cfg(feature = "std")]
91 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
92 #[inline(always)]
93 pub fn decode_instant_from_duration(duration: Duration) -> Instant {
94 let (epoch_dur, instant_now) = init();
95 if duration >= epoch_dur {
96 let delta = duration - epoch_dur;
97 instant_now.checked_add(delta).unwrap_or(instant_now)
98 } else {
99 let delta = epoch_dur - duration;
100 instant_now.checked_sub(delta).unwrap_or(instant_now)
101 }
102 }
103
104 pub use super::duration::{decode_duration, encode_duration};
105 pub use super::option_duration::{decode_option_duration, encode_option_duration};
106}
107
108#[cfg(feature = "std")]
109mod system_time;
110
111#[cfg(feature = "std")]
112#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
113pub use system_time::AtomicSystemTime;
114
115#[cfg(feature = "std")]
116mod option_system_time;
117#[cfg(feature = "std")]
118#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
119pub use option_system_time::AtomicOptionSystemTime;
120
121#[cfg(feature = "std")]
122mod instant;
123#[cfg(feature = "std")]
124#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
125pub use instant::AtomicInstant;
126
127#[cfg(feature = "std")]
128mod option_instant;
129#[cfg(feature = "std")]
130#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
131pub use option_instant::AtomicOptionInstant;
132
133#[cfg(feature = "std")]
134use utils::{decode_instant_from_duration, encode_instant_to_duration};
135
136#[cfg(feature = "arbitrary")]
137mod arbitrary;
138
139#[cfg(feature = "quickcheck")]
140mod quickcheck;
141
142#[cfg(feature = "proptest")]
143mod proptest;