Skip to main content

atomic_time/
option_system_time.rs

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