Skip to main content

universal_time/
global.rs

1#[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
2use core::cell::UnsafeCell;
3#[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
4use core::sync::atomic::{AtomicU8, Ordering};
5
6use crate::{Instant, SystemTime};
7
8/// Source of wall-clock timestamps.
9pub trait WallClock {
10    /// Returns the current wall-clock time, or `None` if not available.
11    fn system_time(&self) -> Option<SystemTime>;
12}
13
14/// Source of monotonic instants.
15pub trait MonotonicClock {
16    /// Returns the current monotonic instant, or `None` if not available.
17    fn instant(&self) -> Option<Instant>;
18}
19
20/// A full time context that can provide wall-clock and monotonic time.
21pub trait TimeContext: WallClock + MonotonicClock + Send + Sync {}
22
23impl<T> TimeContext for T where T: WallClock + MonotonicClock + Send + Sync {}
24
25/// Error returned when attempting to set the global time context more than once.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct GlobalTimeContextAlreadySet;
28
29#[cfg(feature = "std")]
30static GLOBAL_TIME_CONTEXT: std::sync::OnceLock<&'static dyn TimeContext> =
31    std::sync::OnceLock::new();
32
33#[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
34struct NoStdGlobalTimeContext {
35    state: AtomicU8,
36    value: UnsafeCell<Option<&'static dyn TimeContext>>,
37}
38
39#[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
40unsafe impl Sync for NoStdGlobalTimeContext {}
41
42#[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
43impl NoStdGlobalTimeContext {
44    const UNINITIALIZED: u8 = 0;
45    const INITIALIZING: u8 = 1;
46    const READY: u8 = 2;
47
48    const fn new() -> Self {
49        Self {
50            state: AtomicU8::new(Self::UNINITIALIZED),
51            value: UnsafeCell::new(None),
52        }
53    }
54
55    fn set(&self, context: &'static dyn TimeContext) -> Result<(), GlobalTimeContextAlreadySet> {
56        match self.state.compare_exchange(
57            Self::UNINITIALIZED,
58            Self::INITIALIZING,
59            Ordering::AcqRel,
60            Ordering::Acquire,
61        ) {
62            Ok(_) => {
63                unsafe {
64                    *self.value.get() = Some(context);
65                }
66                self.state.store(Self::READY, Ordering::Release);
67                Ok(())
68            }
69            Err(_) => {
70                while self.state.load(Ordering::Acquire) == Self::INITIALIZING {
71                    core::hint::spin_loop();
72                }
73                Err(GlobalTimeContextAlreadySet)
74            }
75        }
76    }
77
78    fn get(&self) -> Option<&'static dyn TimeContext> {
79        let mut state = self.state.load(Ordering::Acquire);
80        while state == Self::INITIALIZING {
81            core::hint::spin_loop();
82            state = self.state.load(Ordering::Acquire);
83        }
84
85        if state == Self::READY {
86            unsafe { *self.value.get() }
87        } else {
88            None
89        }
90    }
91}
92
93#[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
94static GLOBAL_TIME_CONTEXT: NoStdGlobalTimeContext = NoStdGlobalTimeContext::new();
95
96#[cfg(all(not(feature = "std"), not(target_has_atomic = "8")))]
97static mut GLOBAL_TIME_CONTEXT: Option<&'static dyn TimeContext> = None;
98
99/// Installs the global time context.
100///
101/// This can be called only once for the process lifetime.
102pub fn set_global_time_context(
103    context: &'static dyn TimeContext,
104) -> Result<(), GlobalTimeContextAlreadySet> {
105    #[cfg(feature = "std")]
106    {
107        GLOBAL_TIME_CONTEXT
108            .set(context)
109            .map_err(|_| GlobalTimeContextAlreadySet)
110    }
111
112    #[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
113    {
114        GLOBAL_TIME_CONTEXT.set(context)
115    }
116
117    #[cfg(all(not(feature = "std"), not(target_has_atomic = "8")))]
118    {
119        // Fallback for targets without atomics. Call during single-threaded startup
120        // before concurrency begins.
121        unsafe {
122            let current = core::ptr::read(core::ptr::addr_of!(GLOBAL_TIME_CONTEXT));
123            if current.is_some() {
124                Err(GlobalTimeContextAlreadySet)
125            } else {
126                core::ptr::write(core::ptr::addr_of_mut!(GLOBAL_TIME_CONTEXT), Some(context));
127                Ok(())
128            }
129        }
130    }
131}
132
133/// Returns the globally configured time context if one was installed.
134pub fn global_time_context() -> Option<&'static dyn TimeContext> {
135    #[cfg(feature = "std")]
136    {
137        GLOBAL_TIME_CONTEXT.get().copied()
138    }
139
140    #[cfg(all(not(feature = "std"), target_has_atomic = "8"))]
141    {
142        GLOBAL_TIME_CONTEXT.get()
143    }
144
145    #[cfg(all(not(feature = "std"), not(target_has_atomic = "8")))]
146    {
147        // Fallback for targets without atomics. See synchronization note in
148        // `set_global_time_context`.
149        unsafe { core::ptr::read(core::ptr::addr_of!(GLOBAL_TIME_CONTEXT)) }
150    }
151}
152
153#[cfg(any(
154    not(feature = "std"),
155    all(feature = "std", target_family = "wasm", target_os = "unknown")
156))]
157pub(crate) fn panic_missing_system_time() -> ! {
158    panic!(
159        "no wall-clock time source is available; install one with universal_time::set_global_time_context()"
160    )
161}
162
163#[cfg(any(
164    not(feature = "std"),
165    all(feature = "std", target_family = "wasm", target_os = "unknown")
166))]
167pub(crate) fn panic_missing_instant() -> ! {
168    panic!(
169        "no monotonic clock is available; install one with universal_time::set_global_time_context()"
170    )
171}