Skip to main content

atomic_time/
system_time.rs

1use core::sync::atomic::Ordering;
2use std::time::SystemTime;
3
4use crate::AtomicDuration;
5
6/// An atomic version of [`std::time::SystemTime`].
7#[repr(transparent)]
8pub struct AtomicSystemTime(AtomicDuration);
9
10impl core::fmt::Debug for AtomicSystemTime {
11  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
12    f.debug_tuple("AtomicSystemTime")
13      .field(&self.load(Ordering::SeqCst))
14      .finish()
15  }
16}
17
18impl From<SystemTime> for AtomicSystemTime {
19  /// # Panics
20  ///
21  /// Panics if the given `SystemTime` value is earlier than [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
22  #[cfg_attr(not(tarpaulin), inline(always))]
23  fn from(system_time: SystemTime) -> Self {
24    Self::new(system_time)
25  }
26}
27
28impl AtomicSystemTime {
29  /// Returns the system time corresponding to "now".
30  ///
31  /// # Examples
32  /// ```
33  /// use atomic_time::AtomicSystemTime;
34  ///
35  /// let sys_time = AtomicSystemTime::now();
36  /// ```
37  #[cfg_attr(not(tarpaulin), inline(always))]
38  pub fn now() -> Self {
39    Self::new(SystemTime::now())
40  }
41
42  /// Creates a new `AtomicSystemTime` with the given `SystemTime` value.
43  ///
44  /// # Panics
45  ///
46  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
47  #[cfg_attr(not(tarpaulin), inline(always))]
48  pub fn new(system_time: SystemTime) -> Self {
49    Self(AtomicDuration::new(
50      system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
51    ))
52  }
53
54  /// Loads a value from the atomic system time.
55  #[cfg_attr(not(tarpaulin), inline(always))]
56  pub fn load(&self, order: Ordering) -> SystemTime {
57    SystemTime::UNIX_EPOCH + self.0.load(order)
58  }
59
60  /// Stores a value into the atomic system time.
61  ///
62  /// # Panics
63  ///
64  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
65  #[cfg_attr(not(tarpaulin), inline(always))]
66  pub fn store(&self, system_time: SystemTime, order: Ordering) {
67    self.0.store(
68      system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
69      order,
70    )
71  }
72
73  /// Stores a value into the atomic system time, returning the previous value.
74  ///
75  /// # Panics
76  ///
77  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
78  #[cfg_attr(not(tarpaulin), inline(always))]
79  pub fn swap(&self, system_time: SystemTime, order: Ordering) -> SystemTime {
80    SystemTime::UNIX_EPOCH
81      + self.0.swap(
82        system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
83        order,
84      )
85  }
86
87  /// Stores a value into the atomic system time if the current value is the same as the `current`
88  /// value.
89  ///
90  /// # Panics
91  ///
92  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
93  #[cfg_attr(not(tarpaulin), inline(always))]
94  pub fn compare_exchange(
95    &self,
96    current: SystemTime,
97    new: SystemTime,
98    success: Ordering,
99    failure: Ordering,
100  ) -> Result<SystemTime, SystemTime> {
101    match self.0.compare_exchange(
102      current.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
103      new.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
104      success,
105      failure,
106    ) {
107      Ok(duration) => Ok(SystemTime::UNIX_EPOCH + duration),
108      Err(duration) => Err(SystemTime::UNIX_EPOCH + duration),
109    }
110  }
111
112  /// Stores a value into the atomic system time if the current value is the same as the `current`
113  /// value.
114  ///
115  /// # Panics
116  ///
117  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
118  #[cfg_attr(not(tarpaulin), inline(always))]
119  pub fn compare_exchange_weak(
120    &self,
121    current: SystemTime,
122    new: SystemTime,
123    success: Ordering,
124    failure: Ordering,
125  ) -> Result<SystemTime, SystemTime> {
126    match self.0.compare_exchange_weak(
127      current.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
128      new.duration_since(SystemTime::UNIX_EPOCH).unwrap(),
129      success,
130      failure,
131    ) {
132      Ok(duration) => Ok(SystemTime::UNIX_EPOCH + duration),
133      Err(duration) => Err(SystemTime::UNIX_EPOCH + duration),
134    }
135  }
136
137  /// Fetches the value, and applies a function to it that returns an optional
138  /// new value. Returns a `Result` of `Ok(previous_value)` if the function returned `Some(_)`, else
139  /// `Err(previous_value)`.
140  ///
141  /// Note: This may call the function multiple times if the value has been changed from other threads in
142  /// the meantime, as long as the function returns `Some(_)`, but the function will have been applied
143  /// only once to the stored value.
144  ///
145  /// `fetch_update` takes two [`Ordering`] arguments to describe the memory ordering of this operation.
146  /// The first describes the required ordering for when the operation finally succeeds while the second
147  /// describes the required ordering for loads. These correspond to the success and failure orderings of
148  /// [`compare_exchange`] respectively.
149  ///
150  /// Using [`Acquire`](Ordering::Acquire) as success ordering makes the store part
151  /// of this operation [`Relaxed`](Ordering::Relaxed), and using [`Release`](Ordering::Release) makes the final successful load
152  /// [`Relaxed`](Ordering::Relaxed). The (failed) load ordering can only be [`SeqCst`](Ordering::SeqCst), [`Acquire`](Ordering::Acquire) or [`Relaxed`](Ordering::Release)
153  /// and must be equivalent to or weaker than the success ordering.
154  ///
155  /// [`compare_exchange`]: #method.compare_exchange
156  ///
157  /// # Panics
158  ///
159  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
160  ///
161  /// # Examples
162  ///
163  /// ```rust
164  /// use atomic_time::AtomicSystemTime;
165  /// use std::{time::{Duration, SystemTime}, sync::atomic::Ordering};
166  ///
167  /// let now = SystemTime::now();
168  /// let x = AtomicSystemTime::new(now);
169  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None), Err(now));
170  ///
171  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + Duration::from_secs(1))), Ok(now));
172  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + Duration::from_secs(1))), Ok(now + Duration::from_secs(1)));
173  /// assert_eq!(x.load(Ordering::SeqCst), now + Duration::from_secs(2));
174  /// ```
175  #[cfg_attr(not(tarpaulin), inline(always))]
176  pub fn fetch_update<F>(
177    &self,
178    set_order: Ordering,
179    fetch_order: Ordering,
180    mut f: F,
181  ) -> Result<SystemTime, SystemTime>
182  where
183    F: FnMut(SystemTime) -> Option<SystemTime>,
184  {
185    self
186      .0
187      .fetch_update(set_order, fetch_order, |duration| {
188        f(SystemTime::UNIX_EPOCH + duration)
189          .map(|system_time| system_time.duration_since(SystemTime::UNIX_EPOCH).unwrap())
190      })
191      .map(|duration| SystemTime::UNIX_EPOCH + duration)
192      .map_err(|duration| SystemTime::UNIX_EPOCH + duration)
193  }
194
195  /// Returns `true` if operations on values of this type are lock-free.
196  /// If the compiler or the platform doesn't support the necessary
197  /// atomic instructions, global locks for every potentially
198  /// concurrent atomic operation will be used.
199  ///
200  /// # Examples
201  /// ```
202  /// use atomic_time::AtomicSystemTime;
203  ///
204  /// let is_lock_free = AtomicSystemTime::is_lock_free();
205  /// ```
206  #[cfg_attr(not(tarpaulin), inline(always))]
207  pub fn is_lock_free() -> bool {
208    AtomicDuration::is_lock_free()
209  }
210
211  /// Consumes the atomic and returns the contained value.
212  ///
213  /// This is safe because passing `self` by value guarantees that no other threads are
214  /// concurrently accessing the atomic data.
215  #[cfg_attr(not(tarpaulin), inline(always))]
216  pub fn into_inner(self) -> SystemTime {
217    SystemTime::UNIX_EPOCH + self.0.into_inner()
218  }
219}
220
221#[cfg(feature = "serde")]
222const _: () = {
223  use serde::{Deserialize, Serialize};
224
225  impl Serialize for AtomicSystemTime {
226    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
227      self.load(Ordering::SeqCst).serialize(serializer)
228    }
229  }
230
231  impl<'de> Deserialize<'de> for AtomicSystemTime {
232    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
233      Ok(Self::new(SystemTime::deserialize(deserializer)?))
234    }
235  }
236};
237
238#[cfg(test)]
239mod tests {
240  use super::*;
241  use std::time::Duration;
242
243  #[test]
244  fn test_atomic_system_time_now() {
245    let atomic_time = AtomicSystemTime::now();
246    // As SystemTime::now is very precise, this test needs to be lenient with timing.
247    // Just check that the time is not the UNIX_EPOCH.
248    assert_ne!(atomic_time.load(Ordering::SeqCst), SystemTime::UNIX_EPOCH);
249  }
250
251  #[test]
252  fn test_atomic_system_time_new_and_load() {
253    let now = SystemTime::now();
254    let atomic_time = AtomicSystemTime::new(now);
255    assert_eq!(atomic_time.load(Ordering::SeqCst), now);
256  }
257
258  #[test]
259  fn test_atomic_system_time_store_and_load() {
260    let now = SystemTime::now();
261    let after_one_sec = now + Duration::from_secs(1);
262    let atomic_time = AtomicSystemTime::new(now);
263    atomic_time.store(after_one_sec, Ordering::SeqCst);
264    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
265  }
266
267  #[test]
268  fn test_atomic_system_time_swap() {
269    let now = SystemTime::now();
270    let after_one_sec = now + Duration::from_secs(1);
271    let atomic_time = AtomicSystemTime::new(now);
272    let prev_time = atomic_time.swap(after_one_sec, Ordering::SeqCst);
273    assert_eq!(prev_time, now);
274    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
275  }
276
277  #[test]
278  fn test_atomic_system_time_compare_exchange_weak() {
279    let now = SystemTime::now();
280    let after_one_sec = now + Duration::from_secs(1);
281    let after_two_secs = now + Duration::from_secs(2);
282    let atomic_time = AtomicSystemTime::new(now);
283
284    // Successful compare_exchange_weak
285    let mut result;
286    loop {
287      result =
288        atomic_time.compare_exchange_weak(now, after_one_sec, Ordering::SeqCst, Ordering::SeqCst);
289      if result.is_ok() {
290        break;
291      }
292    }
293    assert!(result.is_ok());
294    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
295
296    // Failed compare_exchange_weak
297    let result =
298      atomic_time.compare_exchange_weak(now, after_two_secs, Ordering::SeqCst, Ordering::SeqCst);
299    assert!(result.is_err());
300    assert_eq!(result.unwrap_err(), after_one_sec);
301  }
302
303  #[test]
304  fn test_atomic_system_time_compare_exchange() {
305    let now = SystemTime::now();
306    let after_one_sec = now + Duration::from_secs(1);
307    let atomic_time = AtomicSystemTime::new(now);
308    let result =
309      atomic_time.compare_exchange(now, after_one_sec, Ordering::SeqCst, Ordering::SeqCst);
310    assert!(result.is_ok());
311    assert_eq!(atomic_time.load(Ordering::SeqCst), after_one_sec);
312  }
313
314  #[test]
315  fn test_atomic_system_time_fetch_update() {
316    let now = SystemTime::now();
317    let atomic_time = AtomicSystemTime::new(now);
318
319    // Update the time by adding 1 second
320    let result = atomic_time.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
321      Some(prev + Duration::from_secs(1))
322    });
323    assert!(result.is_ok());
324    assert_eq!(result.unwrap(), now);
325    assert_eq!(
326      atomic_time.load(Ordering::SeqCst),
327      now + Duration::from_secs(1)
328    );
329
330    // Trying an update that returns None, should not change the value
331    let result = atomic_time.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None);
332    assert!(result.is_err());
333    assert_eq!(result.unwrap_err(), now + Duration::from_secs(1));
334    assert_eq!(
335      atomic_time.load(Ordering::SeqCst),
336      now + Duration::from_secs(1)
337    );
338  }
339
340  #[test]
341  #[cfg(feature = "std")]
342  fn test_atomic_system_time_thread_safety() {
343    use std::sync::Arc;
344    use std::thread;
345
346    // Start from a fixed, known value so we can assert an *exact*
347    // final result. The previous version used `load + add + store`,
348    // which loses updates under contention, and asserted only that
349    // the final time was in the future — true even if every
350    // concurrent update was dropped.
351    let start = SystemTime::now();
352    let atomic_time = Arc::new(AtomicSystemTime::new(start));
353    let mut handles = vec![];
354
355    for _ in 0..4 {
356      let atomic_clone = atomic_time.clone();
357      let handle = thread::spawn(move || {
358        atomic_clone
359          .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |current| {
360            Some(current + Duration::from_secs(1))
361          })
362          .expect("closure never returns None");
363      });
364      handles.push(handle);
365    }
366
367    for handle in handles {
368      handle.join().unwrap();
369    }
370
371    // 4 threads × 1 second = 4 seconds, no lost updates.
372    assert_eq!(
373      atomic_time.load(Ordering::SeqCst),
374      start + Duration::from_secs(4)
375    );
376  }
377
378  #[test]
379  fn test_atomic_system_time_debug() {
380    let atomic_time = AtomicSystemTime::now();
381    let debug_str = format!("{:?}", atomic_time);
382    assert!(debug_str.contains("AtomicSystemTime"));
383  }
384
385  #[test]
386  fn test_atomic_system_time_from() {
387    let now = SystemTime::now();
388    let atomic_time = AtomicSystemTime::from(now);
389    assert_eq!(atomic_time.load(Ordering::SeqCst), now);
390  }
391
392  #[test]
393  fn test_atomic_system_time_into_inner() {
394    let now = SystemTime::now();
395    let atomic_time = AtomicSystemTime::new(now);
396    assert_eq!(atomic_time.into_inner(), now);
397  }
398
399  #[test]
400  fn test_atomic_system_time_compare_exchange_failure() {
401    let now = SystemTime::now();
402    let other = now + Duration::from_secs(5);
403    let atomic_time = AtomicSystemTime::new(now);
404    let result = atomic_time.compare_exchange(other, other, Ordering::SeqCst, Ordering::SeqCst);
405    assert!(result.is_err());
406    assert_eq!(result.unwrap_err(), now);
407  }
408
409  #[test]
410  fn test_atomic_system_time_compare_exchange_weak_failure() {
411    let now = SystemTime::now();
412    let other = now + Duration::from_secs(5);
413    let atomic_time = AtomicSystemTime::new(now);
414    let result =
415      atomic_time.compare_exchange_weak(other, other, Ordering::SeqCst, Ordering::SeqCst);
416    assert!(result.is_err());
417    assert_eq!(result.unwrap_err(), now);
418  }
419
420  #[cfg(feature = "serde")]
421  #[test]
422  fn test_atomic_system_time_serde() {
423    use serde::{Deserialize, Serialize};
424
425    #[derive(Serialize, Deserialize)]
426    struct Test {
427      time: AtomicSystemTime,
428    }
429
430    let now = SystemTime::now();
431    let test = Test {
432      time: AtomicSystemTime::new(now),
433    };
434    let serialized = serde_json::to_string(&test).unwrap();
435    let deserialized: Test = serde_json::from_str(&serialized).unwrap();
436    assert_eq!(deserialized.time.load(Ordering::SeqCst), now,);
437  }
438}