Skip to main content

atomic_time/
option_instant.rs

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