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 Default for AtomicOptionSystemTime {
11  /// Equivalent to `Option::<SystemTime>>::None`.
12  #[inline]
13  fn default() -> Self {
14    Self::none()
15  }
16}
17
18impl AtomicOptionSystemTime {
19  /// Equivalent to atomic version `Option::<SystemTime>>::None`.
20  ///
21  /// # Examples
22  ///
23  /// ```rust
24  /// use atomic_time::AtomicOptionSystemTime;
25  ///
26  /// let none = AtomicOptionSystemTime::none();
27  /// assert_eq!(none.load(std::sync::atomic::Ordering::SeqCst), None);
28  /// ```
29  #[inline]
30  pub const fn none() -> Self {
31    Self(AtomicOptionDuration::new(None))
32  }
33
34  /// Returns the system time corresponding to "now".
35  ///
36  /// # Examples
37  /// ```
38  /// use atomic_time::AtomicOptionSystemTime;
39  ///
40  /// let sys_time = AtomicOptionSystemTime::now();
41  /// ```
42  pub fn now() -> Self {
43    Self::new(Some(SystemTime::now()))
44  }
45
46  /// Creates a new `AtomicOptionSystemTime` with the given `SystemTime` value.
47  ///
48  /// # Panics
49  ///
50  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
51  pub fn new(system_time: Option<SystemTime>) -> Self {
52    Self(AtomicOptionDuration::new(
53      system_time.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
54    ))
55  }
56
57  /// Loads a value from the atomic system time.
58  pub fn load(&self, order: Ordering) -> Option<SystemTime> {
59    self.0.load(order).map(|val| SystemTime::UNIX_EPOCH + val)
60  }
61
62  /// Stores a value into the atomic system time.
63  ///
64  /// # Panics
65  ///
66  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
67  pub fn store(&self, system_time: Option<SystemTime>, order: Ordering) {
68    self.0.store(
69      system_time.map(|val| val.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
70      order,
71    )
72  }
73
74  /// Stores a value into the atomic system time, returning the previous value.
75  ///
76  /// # Panics
77  ///
78  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
79  pub fn swap(&self, system_time: Option<SystemTime>, order: Ordering) -> Option<SystemTime> {
80    self
81      .0
82      .swap(
83        system_time.map(|val| val.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
84        order,
85      )
86      .map(|val| SystemTime::UNIX_EPOCH + val)
87  }
88
89  /// Stores a value into the atomic system time if the current value is the same as the `current`
90  /// value.
91  ///
92  /// # Panics
93  ///
94  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
95  pub fn compare_exchange(
96    &self,
97    current: Option<SystemTime>,
98    new: Option<SystemTime>,
99    success: Ordering,
100    failure: Ordering,
101  ) -> Result<Option<SystemTime>, Option<SystemTime>> {
102    match self.0.compare_exchange(
103      current.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
104      new.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
105      success,
106      failure,
107    ) {
108      Ok(duration) => Ok(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
109      Err(duration) => Err(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
110    }
111  }
112
113  /// Stores a value into the atomic system time if the current value is the same as the `current`
114  /// value.
115  ///
116  /// # Panics
117  ///
118  /// If the given `SystemTime` value is earlier than the [`UNIX_EPOCH`](SystemTime::UNIX_EPOCH).
119  pub fn compare_exchange_weak(
120    &self,
121    current: Option<SystemTime>,
122    new: Option<SystemTime>,
123    success: Ordering,
124    failure: Ordering,
125  ) -> Result<Option<SystemTime>, Option<SystemTime>> {
126    match self.0.compare_exchange_weak(
127      current.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
128      new.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()),
129      success,
130      failure,
131    ) {
132      Ok(duration) => Ok(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
133      Err(duration) => Err(duration.map(|d| SystemTime::UNIX_EPOCH + d)),
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::AtomicOptionSystemTime;
165  /// use std::{time::{Duration, SystemTime}, sync::atomic::Ordering};
166  ///
167  /// let now = SystemTime::now();
168  /// let x = AtomicOptionSystemTime::none();
169  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None), Err(None));
170  /// x.store(Some(now), Ordering::SeqCst);
171  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x.map(|val| val + Duration::from_secs(1)))), Ok(Some(now)));
172  /// 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))));
173  /// 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))));
174  /// assert_eq!(x.load(Ordering::SeqCst), Some(now + Duration::from_secs(3)));
175  /// ```
176  pub fn fetch_update<F>(
177    &self,
178    set_order: Ordering,
179    fetch_order: Ordering,
180    mut f: F,
181  ) -> Result<Option<SystemTime>, Option<SystemTime>>
182  where
183    F: FnMut(Option<SystemTime>) -> Option<Option<SystemTime>>,
184  {
185    self
186      .0
187      .fetch_update(set_order, fetch_order, |duration| {
188        f(duration.map(|d| SystemTime::UNIX_EPOCH + d))
189          .map(|system_time| system_time.map(|d| d.duration_since(SystemTime::UNIX_EPOCH).unwrap()))
190      })
191      .map(|duration| duration.map(|d| SystemTime::UNIX_EPOCH + d))
192      .map_err(|duration| duration.map(|d| SystemTime::UNIX_EPOCH + d))
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::AtomicOptionSystemTime;
203  ///
204  /// let is_lock_free = AtomicOptionSystemTime::is_lock_free();
205  /// ```
206  #[inline]
207  pub fn is_lock_free() -> bool {
208    AtomicOptionDuration::is_lock_free()
209  }
210}
211
212#[cfg(feature = "serde")]
213const _: () = {
214  use serde::{Deserialize, Serialize};
215
216  impl Serialize for AtomicOptionSystemTime {
217    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
218      self.load(Ordering::SeqCst).serialize(serializer)
219    }
220  }
221
222  impl<'de> Deserialize<'de> for AtomicOptionSystemTime {
223    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
224      Ok(Self::new(Option::<SystemTime>::deserialize(deserializer)?))
225    }
226  }
227};
228
229#[cfg(test)]
230mod tests {
231  use super::*;
232  use std::time::Duration;
233
234  #[test]
235  fn test_atomic_option_system_time_now() {
236    let atomic_time = AtomicOptionSystemTime::now();
237    // Check that the time is not None and close to the current time.
238    assert!(atomic_time.load(Ordering::SeqCst).is_some());
239  }
240
241  #[test]
242  fn test_atomic_option_system_time_none() {
243    let atomic_time = AtomicOptionSystemTime::none();
244    assert_eq!(atomic_time.load(Ordering::SeqCst), None);
245  }
246
247  #[test]
248  fn test_atomic_option_system_time_new_and_load() {
249    let now = SystemTime::now();
250    let atomic_time = AtomicOptionSystemTime::new(Some(now));
251    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(now));
252  }
253
254  #[test]
255  fn test_atomic_option_system_time_store_and_load() {
256    let now = SystemTime::now();
257    let after_one_sec = now + Duration::from_secs(1);
258    let atomic_time = AtomicOptionSystemTime::new(Some(now));
259    atomic_time.store(Some(after_one_sec), Ordering::SeqCst);
260    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
261  }
262
263  #[test]
264  fn test_atomic_option_system_time_swap() {
265    let now = SystemTime::now();
266    let after_one_sec = now + Duration::from_secs(1);
267    let atomic_time = AtomicOptionSystemTime::new(Some(now));
268    let prev_time = atomic_time.swap(Some(after_one_sec), Ordering::SeqCst);
269    assert_eq!(prev_time, Some(now));
270    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
271  }
272
273  #[test]
274  fn test_atomic_option_system_time_compare_exchange() {
275    let now = SystemTime::now();
276    let after_one_sec = now + Duration::from_secs(1);
277    let atomic_time = AtomicOptionSystemTime::new(Some(now));
278    let result = atomic_time.compare_exchange(
279      Some(now),
280      Some(after_one_sec),
281      Ordering::SeqCst,
282      Ordering::SeqCst,
283    );
284    assert!(result.is_ok());
285    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
286  }
287
288  #[test]
289  fn test_atomic_option_system_time_compare_exchange_weak() {
290    let now = SystemTime::now();
291    let after_one_sec = now + Duration::from_secs(1);
292    let atomic_time = AtomicOptionSystemTime::new(Some(now));
293
294    let mut result;
295    loop {
296      result = atomic_time.compare_exchange_weak(
297        Some(now),
298        Some(after_one_sec),
299        Ordering::SeqCst,
300        Ordering::SeqCst,
301      );
302      if result.is_ok() {
303        break;
304      }
305    }
306    assert!(result.is_ok());
307    assert_eq!(atomic_time.load(Ordering::SeqCst), Some(after_one_sec));
308  }
309
310  #[test]
311  fn test_atomic_option_system_time_fetch_update() {
312    let now = SystemTime::now();
313    let atomic_time = AtomicOptionSystemTime::new(Some(now));
314
315    let result = atomic_time.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
316      Some(prev.map(|val| val + Duration::from_secs(1)))
317    });
318    assert!(result.is_ok());
319    assert_eq!(result.unwrap(), Some(now));
320    assert_eq!(
321      atomic_time.load(Ordering::SeqCst),
322      Some(now + Duration::from_secs(1))
323    );
324  }
325
326  #[test]
327  fn test_atomic_option_system_time_thread_safety() {
328    use std::sync::Arc;
329    use std::thread;
330
331    let atomic_time = Arc::new(AtomicOptionSystemTime::now());
332    let mut handles = vec![];
333
334    // Spawn multiple threads to update the time
335    for _ in 0..4 {
336      let atomic_clone = Arc::clone(&atomic_time);
337      let handle = thread::spawn(move || {
338        let current = atomic_clone.load(Ordering::SeqCst);
339        if let Some(current_time) = current {
340          // Adding 1 second to the current time
341          let new_time = current_time + Duration::from_secs(1);
342          atomic_clone.store(Some(new_time), Ordering::SeqCst);
343        }
344      });
345      handles.push(handle);
346    }
347
348    // Wait for all threads to complete
349    for handle in handles {
350      handle.join().unwrap();
351    }
352
353    // Verify that the time has been updated
354    if let Some(updated_time) = atomic_time.load(Ordering::SeqCst) {
355      assert!(updated_time > SystemTime::now() - Duration::from_secs(4));
356    } else {
357      panic!("AtomicOptionSystemTime should not be None");
358    }
359  }
360
361  #[cfg(feature = "serde")]
362  #[test]
363  fn test_atomic_system_time_serde() {
364    use serde::{Deserialize, Serialize};
365
366    #[derive(Serialize, Deserialize)]
367    struct Test {
368      time: AtomicOptionSystemTime,
369    }
370
371    let now = SystemTime::now();
372    let test = Test {
373      time: AtomicOptionSystemTime::new(Some(now)),
374    };
375    let serialized = serde_json::to_string(&test).unwrap();
376    let deserialized: Test = serde_json::from_str(&serialized).unwrap();
377    assert_eq!(deserialized.time.load(Ordering::SeqCst), Some(now));
378  }
379}