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  #[inline]
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  #[inline]
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  #[inline]
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  pub fn now() -> Self {
59    Self::new(Some(SystemTime::now()))
60  }
61
62  /// Creates a new `AtomicOptionSystemTime` with the given `SystemTime` value.
63  ///
64  /// # Panics
65  ///
66  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
67  pub fn new(system_time: Option<SystemTime>) -> Self {
68    Self(AtomicOptionDuration::new(
69      system_time.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
70    ))
71  }
72
73  /// Loads a value from the atomic system time.
74  pub fn load(&self, order: Ordering) -> Option<SystemTime> {
75    self.0.load(order).map(|val| SystemTime::UNIX_EPOCH + val)
76  }
77
78  /// Stores a value into the atomic system time.
79  ///
80  /// # Panics
81  ///
82  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
83  pub fn store(&self, system_time: Option<SystemTime>, order: Ordering) {
84    self.0.store(
85      system_time.map(|val| val.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
86      order,
87    )
88  }
89
90  /// Stores a value into the atomic system time, returning the previous value.
91  ///
92  /// # Panics
93  ///
94  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
95  pub fn swap(&self, system_time: Option<SystemTime>, order: Ordering) -> Option<SystemTime> {
96    self
97      .0
98      .swap(
99        system_time.map(|val| val.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
100        order,
101      )
102      .map(|val| SystemTime::UNIX_EPOCH + val)
103  }
104
105  /// Stores a value into the atomic system time if the current value is the same as the `current`
106  /// value.
107  ///
108  /// # Panics
109  ///
110  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
111  pub fn compare_exchange(
112    &self,
113    current: Option<SystemTime>,
114    new: Option<SystemTime>,
115    success: Ordering,
116    failure: Ordering,
117  ) -> Result<Option<SystemTime>, Option<SystemTime>> {
118    match self.0.compare_exchange(
119      current.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
120      new.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
121      success,
122      failure,
123    ) {
124      Ok(duration) => Ok(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
125      Err(duration) => Err(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
126    }
127  }
128
129  /// Stores a value into the atomic system time if the current value is the same as the `current`
130  /// value.
131  ///
132  /// # Panics
133  ///
134  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
135  pub fn compare_exchange_weak(
136    &self,
137    current: Option<SystemTime>,
138    new: Option<SystemTime>,
139    success: Ordering,
140    failure: Ordering,
141  ) -> Result<Option<SystemTime>, Option<SystemTime>> {
142    match self.0.compare_exchange_weak(
143      current.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
144      new.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
145      success,
146      failure,
147    ) {
148      Ok(duration) => Ok(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
149      Err(duration) => Err(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
150    }
151  }
152
153  /// Fetches the value, and applies a function to it that returns an optional
154  /// new value. Returns a `Result` of `Ok(previous_value)` if the function returned `Some(_)`, else
155  /// `Err(previous_value)`.
156  ///
157  /// Note: This may call the function multiple times if the value has been changed from other threads in
158  /// the meantime, as long as the function returns `Some(_)`, but the function will have been applied
159  /// only once to the stored value.
160  ///
161  /// `fetch_update` takes two [`Ordering`] arguments to describe the memory ordering of this operation.
162  /// The first describes the required ordering for when the operation finally succeeds while the second
163  /// describes the required ordering for loads. These correspond to the success and failure orderings of
164  /// [`compare_exchange`] respectively.
165  ///
166  /// Using [`Acquire`](Ordering::Acquire) as success ordering makes the store part
167  /// of this operation [`Relaxed`](Ordering::Relaxed), and using [`Release`](Ordering::Release) makes the final successful load
168  /// [`Relaxed`](Ordering::Relaxed). The (failed) load ordering can only be [`SeqCst`](Ordering::SeqCst), [`Acquire`](Ordering::Acquire) or [`Relaxed`](Ordering::Release)
169  /// and must be equivalent to or weaker than the success ordering.
170  ///
171  /// [`compare_exchange`]: #method.compare_exchange
172  ///
173  /// # Panics
174  ///
175  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
176  ///
177  /// # Examples
178  ///
179  /// ```rust
180  /// use atomic_time::AtomicOptionSystemTime;
181  /// use std::{time::{Duration, SystemTime}, sync::atomic::Ordering};
182  ///
183  /// let now = SystemTime::now();
184  /// let x = AtomicOptionSystemTime::none();
185  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None), Err(None));
186  /// x.store(Some(now), Ordering::SeqCst);
187  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x.map(|val| val + Duration::from_secs(1)))), Ok(Some(now)));
188  /// 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))));
189  /// 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))));
190  /// assert_eq!(x.load(Ordering::SeqCst), Some(now + Duration::from_secs(3)));
191  /// ```
192  pub fn fetch_update<F>(
193    &self,
194    set_order: Ordering,
195    fetch_order: Ordering,
196    mut f: F,
197  ) -> Result<Option<SystemTime>, Option<SystemTime>>
198  where
199    F: FnMut(Option<SystemTime>) -> Option<Option<SystemTime>>,
200  {
201    self
202      .0
203      .fetch_update(set_order, fetch_order, |duration| {
204        f(duration.map(|d| SystemTime::UNIX_EPOCH + d))
205          .map(|system_time| system_time.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()))
206      })
207      .map(|duration| duration.map(|d| SystemTime::UNIX_EPOCH + d))
208      .map_err(|duration| duration.map(|d| SystemTime::UNIX_EPOCH + d))
209  }
210
211  /// Returns `true` if operations on values of this type are lock-free.
212  /// If the compiler or the platform doesn't support the necessary
213  /// atomic instructions, global locks for every potentially
214  /// concurrent atomic operation will be used.
215  ///
216  /// # Examples
217  /// ```
218  /// use atomic_time::AtomicOptionSystemTime;
219  ///
220  /// let is_lock_free = AtomicOptionSystemTime::is_lock_free();
221  /// ```
222  #[inline]
223  pub fn is_lock_free() -> bool {
224    AtomicOptionDuration::is_lock_free()
225  }
226
227  /// Consumes the atomic and returns the contained value.
228  ///
229  /// This is safe because passing `self` by value guarantees that no other threads are
230  /// concurrently accessing the atomic data.
231  #[inline]
232  pub fn into_inner(self) -> Option<SystemTime> {
233    self.0.into_inner().map(|d| SystemTime::UNIX_EPOCH + d)
234  }
235}
236
237#[cfg(feature = "serde")]
238const _: () = {
239  use serde::{Deserialize, Serialize};
240
241  impl Serialize for AtomicOptionSystemTime {
242    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
243      self.load(Ordering::SeqCst).serialize(serializer)
244    }
245  }
246
247  impl<'de> Deserialize<'de> for AtomicOptionSystemTime {
248    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
249      Ok(Self::new(Option::<SystemTime>::deserialize(deserializer)?))
250    }
251  }
252};
253
254#[cfg(test)]
255mod tests {
256  use super::*;
257  use std::time::Duration;
258
259  #[test]
260  fn test_atomic_option_system_time_now() {
261    let atomic_time = AtomicOptionSystemTime::now();
262    // Check that the time is not None and close to the current time.
263    assert!(atomic_time.load(Ordering::SeqCst).is_some());
264  }
265
266  #[test]
267  fn test_atomic_option_system_time_none() {
268    let atomic_time = AtomicOptionSystemTime::none();
269    assert_eq!(atomic_time.load(Ordering::SeqCst), None);
270  }
271
272  #[test]
273  fn test_atomic_option_system_time_new_and_load() {
274    let now = SystemTime::now();
275    let atomic_time = AtomicOptionSystemTime::new(Some(now));
276    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(now));
277  }
278
279  #[test]
280  fn test_atomic_option_system_time_store_and_load() {
281    let now = SystemTime::now();
282    let after_one_sec = now + Duration::from_secs(1);
283    let atomic_time = AtomicOptionSystemTime::new(Some(now));
284    atomic_time.store(Some(after_one_sec), Ordering::SeqCst);
285    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
286  }
287
288  #[test]
289  fn test_atomic_option_system_time_swap() {
290    let now = SystemTime::now();
291    let after_one_sec = now + Duration::from_secs(1);
292    let atomic_time = AtomicOptionSystemTime::new(Some(now));
293    let prev_time = atomic_time.swap(Some(after_one_sec), Ordering::SeqCst);
294    assert_eq!(prev_time, Some(now));
295    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
296  }
297
298  #[test]
299  fn test_atomic_option_system_time_compare_exchange() {
300    let now = SystemTime::now();
301    let after_one_sec = now + Duration::from_secs(1);
302    let atomic_time = AtomicOptionSystemTime::new(Some(now));
303    let result = atomic_time.compare_exchange(
304      Some(now),
305      Some(after_one_sec),
306      Ordering::SeqCst,
307      Ordering::SeqCst,
308    );
309    assert!(result.is_ok());
310    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
311  }
312
313  #[test]
314  fn test_atomic_option_system_time_compare_exchange_weak() {
315    let now = SystemTime::now();
316    let after_one_sec = now + Duration::from_secs(1);
317    let atomic_time = AtomicOptionSystemTime::new(Some(now));
318
319    let mut result;
320    loop {
321      result = atomic_time.compare_exchange_weak(
322        Some(now),
323        Some(after_one_sec),
324        Ordering::SeqCst,
325        Ordering::SeqCst,
326      );
327      if result.is_ok() {
328        break;
329      }
330    }
331    assert!(result.is_ok());
332    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
333  }
334
335  #[test]
336  fn test_atomic_option_system_time_fetch_update() {
337    let now = SystemTime::now();
338    let atomic_time = AtomicOptionSystemTime::new(Some(now));
339
340    let result = atomic_time.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
341      Some(prev.map(|val| val + Duration::from_secs(1)))
342    });
343    assert!(result.is_ok());
344    assert_eq!(result.unwrap(), Some(now));
345    assert_eq!(
346      atomic_time.load(Ordering::SeqCst),
347      Some(now + Duration::from_secs(1))
348    );
349  }
350
351  #[test]
352  fn test_atomic_option_system_time_thread_safety() {
353    use std::sync::Arc;
354    use std::thread;
355
356    let atomic_time = Arc::new(AtomicOptionSystemTime::now());
357    let mut handles = vec![];
358
359    // Spawn multiple threads to update the time
360    for _ in 0..4 {
361      let atomic_clone = Arc::clone(&atomic_time);
362      let handle = thread::spawn(move || {
363        let current = atomic_clone.load(Ordering::SeqCst);
364        if let Some(current_time) = current {
365          // Adding 1 second to the current time
366          let new_time = current_time + Duration::from_secs(1);
367          atomic_clone.store(Some(new_time), Ordering::SeqCst);
368        }
369      });
370      handles.push(handle);
371    }
372
373    // Wait for all threads to complete
374    for handle in handles {
375      handle.join().unwrap();
376    }
377
378    // Verify that the time has been updated
379    if let Some(updated_time) = atomic_time.load(Ordering::SeqCst) {
380      assert!(updated_time > SystemTime::now() - Duration::from_secs(4));
381    } else {
382      panic!("AtomicOptionSystemTime should not be None");
383    }
384  }
385
386  #[cfg(feature = "serde")]
387  #[test]
388  fn test_atomic_system_time_serde() {
389    use serde::{Deserialize, Serialize};
390
391    #[derive(Serialize, Deserialize)]
392    struct Test {
393      time: AtomicOptionSystemTime,
394    }
395
396    let now = SystemTime::now();
397    let test = Test {
398      time: AtomicOptionSystemTime::new(Some(now)),
399    };
400    let serialized = serde_json::to_string(&test).unwrap();
401    let deserialized: Test = serde_json::from_str(&serialized).unwrap();
402    assert_eq!(deserialized.time.load(Ordering::SeqCst), Some(now));
403  }
404}