Skip to main content

atomic_time/
option_duration.rs

1use crate::AtomicU128;
2use core::{sync::atomic::Ordering, time::Duration};
3
4/// Atomic version of [`Option<Duration>`].
5#[repr(transparent)]
6pub struct AtomicOptionDuration(AtomicU128);
7impl core::fmt::Debug for AtomicOptionDuration {
8  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
9    f.debug_tuple("AtomicOptionDuration")
10      .field(&self.load(Ordering::SeqCst))
11      .finish()
12  }
13}
14impl Default for AtomicOptionDuration {
15  /// Creates an `AtomicOptionDuration` initialized to `None`.
16  #[cfg_attr(not(tarpaulin), inline(always))]
17  fn default() -> Self {
18    Self::none()
19  }
20}
21impl From<Option<Duration>> for AtomicOptionDuration {
22  #[cfg_attr(not(tarpaulin), inline(always))]
23  fn from(duration: Option<Duration>) -> Self {
24    Self::new(duration)
25  }
26}
27impl AtomicOptionDuration {
28  /// Creates a new `AtomicOptionDuration` with `None`.
29  #[cfg_attr(not(tarpaulin), inline(always))]
30  pub const fn none() -> Self {
31    Self(AtomicU128::new(encode_option_duration(None)))
32  }
33
34  /// Creates a new `AtomicOptionDuration` with the given value.
35  #[cfg_attr(not(tarpaulin), inline(always))]
36  pub const fn new(duration: Option<Duration>) -> Self {
37    Self(AtomicU128::new(encode_option_duration(duration)))
38  }
39  /// Loads `Option<Duration>` from `AtomicOptionDuration`.
40  ///
41  /// load takes an [`Ordering`] argument which describes the memory ordering of this operation.
42  ///
43  /// # Panics
44  /// Panics if order is [`Release`](Ordering::Release) or [`AcqRel`](Ordering::AcqRel).
45  #[cfg_attr(not(tarpaulin), inline(always))]
46  pub fn load(&self, ordering: Ordering) -> Option<Duration> {
47    decode_option_duration(self.0.load(ordering))
48  }
49  /// Stores a value into the `AtomicOptionDuration`.
50  ///
51  /// `store` takes an [`Ordering`] argument which describes the memory ordering
52  /// of this operation.
53  ///
54  /// # Panics
55  ///
56  /// Panics if `order` is [`Acquire`](Ordering::Acquire) or [`AcqRel`](Ordering::AcqRel).
57  #[cfg_attr(not(tarpaulin), inline(always))]
58  pub fn store(&self, val: Option<Duration>, ordering: Ordering) {
59    self.0.store(encode_option_duration(val), ordering)
60  }
61  /// Stores a value into the `AtomicOptionDuration`, returning the old value.
62  ///
63  /// `swap` takes an [`Ordering`] argument which describes the memory ordering
64  /// of this operation.
65  #[cfg_attr(not(tarpaulin), inline(always))]
66  pub fn swap(&self, val: Option<Duration>, ordering: Ordering) -> Option<Duration> {
67    decode_option_duration(self.0.swap(encode_option_duration(val), ordering))
68  }
69  /// Stores a value into the `AtomicOptionDuration` if the current value is the same as the
70  /// `current` value.
71  ///
72  /// Unlike [`compare_exchange`], this function is allowed to spuriously fail
73  /// even when the comparison succeeds, which can result in more efficient
74  /// code on some platforms. The return value is a result indicating whether
75  /// the new value was written and containing the previous value.
76  ///
77  /// `compare_exchange` takes two [`Ordering`] arguments to describe the memory
78  /// ordering of this operation. The first describes the required ordering if
79  /// the operation succeeds while the second describes the required ordering
80  /// when the operation fails. The failure ordering can't be [`Release`](Ordering::Release) or
81  /// [`AcqRel`](Ordering::AcqRel) and must be equivalent or weaker than the success ordering.
82  /// success ordering.
83  ///
84  /// [`compare_exchange`]: #method.compare_exchange
85  #[cfg_attr(not(tarpaulin), inline(always))]
86  pub fn compare_exchange_weak(
87    &self,
88    current: Option<Duration>,
89    new: Option<Duration>,
90    success: Ordering,
91    failure: Ordering,
92  ) -> Result<Option<Duration>, Option<Duration>> {
93    self
94      .0
95      .compare_exchange_weak(
96        encode_option_duration(current),
97        encode_option_duration(new),
98        success,
99        failure,
100      )
101      .map(decode_option_duration)
102      .map_err(decode_option_duration)
103  }
104  /// Stores a value into the `AtomicOptionDuration` if the current value is the same as the
105  /// `current` value.
106  ///
107  /// The return value is a result indicating whether the new value was
108  /// written and containing the previous value. On success this value is
109  /// guaranteed to be equal to `new`.
110  ///
111  /// [`compare_exchange`] takes two [`Ordering`] arguments to describe the memory
112  /// ordering of this operation. The first describes the required ordering if
113  /// the operation succeeds while the second describes the required ordering
114  /// when the operation fails. The failure ordering can't be [`Release`](Ordering::Release) or
115  /// [`AcqRel`](Ordering::AcqRel) and must be equivalent or weaker than the success ordering.
116  ///
117  /// [`compare_exchange`]: #method.compare_exchange
118  #[cfg_attr(not(tarpaulin), inline(always))]
119  pub fn compare_exchange(
120    &self,
121    current: Option<Duration>,
122    new: Option<Duration>,
123    success: Ordering,
124    failure: Ordering,
125  ) -> Result<Option<Duration>, Option<Duration>> {
126    self
127      .0
128      .compare_exchange(
129        encode_option_duration(current),
130        encode_option_duration(new),
131        success,
132        failure,
133      )
134      .map(decode_option_duration)
135      .map_err(decode_option_duration)
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  /// # Examples
158  ///
159  /// ```rust
160  /// use atomic_time::AtomicOptionDuration;
161  /// use std::{time::Duration, sync::atomic::Ordering};
162  ///
163  /// let x = AtomicOptionDuration::new(Some(Duration::from_secs(7)));
164  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None), Err(Some(Duration::from_secs(7))));
165  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x.map(|val| val + Duration::from_secs(1)))), Ok(Some(Duration::from_secs(7))));
166  /// assert_eq!(x.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x.map(|val| val + Duration::from_secs(1)))), Ok(Some(Duration::from_secs(8))));
167  /// assert_eq!(x.load(Ordering::SeqCst), Some(Duration::from_secs(9)));
168  /// ```
169  #[cfg_attr(not(tarpaulin), inline(always))]
170  pub fn fetch_update<F>(
171    &self,
172    set_order: Ordering,
173    fetch_order: Ordering,
174    mut f: F,
175  ) -> Result<Option<Duration>, Option<Duration>>
176  where
177    F: FnMut(Option<Duration>) -> Option<Option<Duration>>,
178  {
179    self
180      .0
181      .fetch_update(set_order, fetch_order, |d| {
182        f(decode_option_duration(d)).map(encode_option_duration)
183      })
184      .map(decode_option_duration)
185      .map_err(decode_option_duration)
186  }
187  /// Consumes the atomic and returns the contained value.
188  ///
189  /// This is safe because passing `self` by value guarantees that no other threads are
190  /// concurrently accessing the atomic data.
191  #[cfg_attr(not(tarpaulin), inline(always))]
192  pub fn into_inner(self) -> Option<Duration> {
193    decode_option_duration(self.0.into_inner())
194  }
195
196  /// Returns `true` if operations on values of this type are lock-free.
197  /// If the compiler or the platform doesn't support the necessary
198  /// atomic instructions, global locks for every potentially
199  /// concurrent atomic operation will be used.
200  ///
201  /// # Examples
202  /// ```
203  /// use atomic_time::AtomicOptionDuration;
204  ///
205  /// let is_lock_free = AtomicOptionDuration::is_lock_free();
206  /// ```
207  #[cfg_attr(not(tarpaulin), inline(always))]
208  pub fn is_lock_free() -> bool {
209    AtomicU128::is_lock_free()
210  }
211}
212
213/// Encode an [`Option<Duration>`] into an [`u128`].
214#[cfg_attr(not(tarpaulin), inline(always))]
215pub const fn encode_option_duration(option_duration: Option<Duration>) -> u128 {
216  match option_duration {
217    Some(duration) => {
218      let seconds = duration.as_secs() as u128;
219      let nanos = duration.subsec_nanos() as u128;
220      (1 << 127) | (seconds << 32) | nanos
221    }
222    None => 0,
223  }
224}
225
226/// Decode an [`Option<Duration>`] from an encoded [`u128`].
227///
228/// Accepts non-canonical input without panicking. The `Some(_)`
229/// encoding stores 64 bits of seconds in bits 32..=95 and up to 30
230/// bits of nanoseconds in bits 0..=31 (bit 127 is the Some/None
231/// discriminant, bits 96..=126 are unused). If the decoded nanosecond
232/// count is 10⁹ or more — which `encode_option_duration` never
233/// produces, but which can appear when the encoded value comes from
234/// corrupted storage or untrusted input — the extra whole seconds are
235/// folded into the seconds field; if that push past `u64::MAX`, the
236/// result saturates at [`Duration::MAX`].
237///
238/// This means `decode_option_duration(u128::MAX)` yields
239/// `Some(Duration::MAX)` rather than panicking as the previous
240/// implementation did.
241#[cfg_attr(not(tarpaulin), inline(always))]
242pub const fn decode_option_duration(encoded: u128) -> Option<Duration> {
243  if encoded >> 127 == 0 {
244    None
245  } else {
246    let seconds = ((encoded << 1) >> 33) as u64;
247    let raw_nanos = (encoded & 0xFFFFFFFF) as u32;
248    let extra_secs = (raw_nanos / 1_000_000_000) as u64;
249    let nanos = raw_nanos % 1_000_000_000;
250    Some(match seconds.checked_add(extra_secs) {
251      Some(secs) => Duration::new(secs, nanos),
252      None => Duration::new(u64::MAX, 999_999_999),
253    })
254  }
255}
256
257#[cfg(feature = "serde")]
258const _: () = {
259  use serde::{Deserialize, Serialize};
260
261  impl Serialize for AtomicOptionDuration {
262    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
263      self.load(Ordering::SeqCst).serialize(serializer)
264    }
265  }
266
267  impl<'de> Deserialize<'de> for AtomicOptionDuration {
268    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
269      Ok(Self::new(Option::<Duration>::deserialize(deserializer)?))
270    }
271  }
272};
273
274#[cfg(test)]
275mod tests {
276  use super::*;
277
278  #[test]
279  fn test_new_atomic_option_duration() {
280    let duration = Duration::from_secs(5);
281    let atomic_duration = AtomicOptionDuration::new(Some(duration));
282    assert_eq!(atomic_duration.load(Ordering::SeqCst), Some(duration));
283  }
284
285  #[test]
286  fn test_atomic_option_duration_load() {
287    let duration = Duration::from_secs(10);
288    let atomic_duration = AtomicOptionDuration::new(Some(duration));
289    assert_eq!(atomic_duration.load(Ordering::SeqCst), Some(duration));
290  }
291
292  #[test]
293  fn test_atomic_option_duration_store() {
294    let initial_duration = Duration::from_secs(3);
295    let new_duration = Duration::from_secs(7);
296    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
297    atomic_duration.store(Some(new_duration), Ordering::SeqCst);
298    assert_eq!(atomic_duration.load(Ordering::SeqCst), Some(new_duration));
299  }
300
301  #[test]
302  fn test_atomic_option_duration_store_none() {
303    let initial_duration = Duration::from_secs(3);
304    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
305    atomic_duration.store(None, Ordering::SeqCst);
306    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
307  }
308
309  #[test]
310  fn test_atomic_option_duration_swap() {
311    let initial_duration = Duration::from_secs(2);
312    let new_duration = Duration::from_secs(8);
313    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
314    let prev_duration = atomic_duration.swap(Some(new_duration), Ordering::SeqCst);
315    assert_eq!(prev_duration, Some(initial_duration));
316    assert_eq!(atomic_duration.load(Ordering::SeqCst), Some(new_duration));
317  }
318
319  #[test]
320  fn test_atomic_option_duration_compare_exchange_weak() {
321    let initial_duration = Duration::from_secs(4);
322    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
323
324    // Successful exchange
325    let mut result;
326    loop {
327      result = atomic_duration.compare_exchange_weak(
328        Some(initial_duration),
329        Some(Duration::from_secs(6)),
330        Ordering::SeqCst,
331        Ordering::SeqCst,
332      );
333
334      if result.is_ok() {
335        break;
336      }
337    }
338
339    assert!(result.is_ok());
340    assert_eq!(result.unwrap(), Some(initial_duration));
341    assert_eq!(
342      atomic_duration.load(Ordering::SeqCst),
343      Some(Duration::from_secs(6))
344    );
345
346    // Failed exchange
347    let result = atomic_duration.compare_exchange_weak(
348      Some(initial_duration),
349      Some(Duration::from_secs(7)),
350      Ordering::SeqCst,
351      Ordering::SeqCst,
352    );
353    assert!(result.is_err());
354    assert_eq!(result.unwrap_err(), Some(Duration::from_secs(6)));
355  }
356
357  #[test]
358  fn test_atomic_option_duration_compare_exchange() {
359    let initial_duration = Duration::from_secs(1);
360    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
361
362    // Successful exchange
363    let result = atomic_duration.compare_exchange(
364      Some(initial_duration),
365      Some(Duration::from_secs(5)),
366      Ordering::SeqCst,
367      Ordering::SeqCst,
368    );
369    assert!(result.is_ok());
370    assert_eq!(result.unwrap(), Some(initial_duration));
371    assert_eq!(
372      atomic_duration.load(Ordering::SeqCst),
373      Some(Duration::from_secs(5))
374    );
375
376    // Failed exchange
377    let result = atomic_duration.compare_exchange(
378      Some(initial_duration),
379      Some(Duration::from_secs(6)),
380      Ordering::SeqCst,
381      Ordering::SeqCst,
382    );
383    assert!(result.is_err());
384    assert_eq!(result.unwrap_err(), Some(Duration::from_secs(5)));
385  }
386
387  #[test]
388  fn test_atomic_option_duration_with_none_initially() {
389    let atomic_duration = AtomicOptionDuration::new(None);
390    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
391  }
392
393  #[test]
394  fn test_atomic_option_duration_store_none_and_then_value() {
395    let atomic_duration = AtomicOptionDuration::new(None);
396    atomic_duration.store(Some(Duration::from_secs(5)), Ordering::SeqCst);
397    assert_eq!(
398      atomic_duration.load(Ordering::SeqCst),
399      Some(Duration::from_secs(5))
400    );
401  }
402
403  #[test]
404  fn test_atomic_option_duration_swap_with_none() {
405    let initial_duration = Duration::from_secs(2);
406    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
407    let prev_duration = atomic_duration.swap(None, Ordering::SeqCst);
408    assert_eq!(prev_duration, Some(initial_duration));
409    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
410  }
411
412  #[test]
413  fn test_atomic_option_duration_compare_exchange_weak_with_none() {
414    let initial_duration = Duration::from_secs(4);
415    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
416
417    // Change to None
418    let mut result;
419
420    loop {
421      result = atomic_duration.compare_exchange_weak(
422        Some(initial_duration),
423        None,
424        Ordering::SeqCst,
425        Ordering::SeqCst,
426      );
427
428      if result.is_ok() {
429        break;
430      }
431    }
432
433    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
434
435    // Change back to Some(Duration)
436    let new_duration = Duration::from_secs(6);
437    let mut result;
438
439    loop {
440      result = atomic_duration.compare_exchange_weak(
441        None,
442        Some(new_duration),
443        Ordering::SeqCst,
444        Ordering::SeqCst,
445      );
446      if result.is_ok() {
447        break;
448      }
449    }
450
451    assert_eq!(atomic_duration.load(Ordering::SeqCst), Some(new_duration));
452  }
453
454  #[test]
455  fn test_atomic_option_duration_compare_exchange_with_none() {
456    let initial_duration = Duration::from_secs(1);
457    let atomic_duration = AtomicOptionDuration::new(Some(initial_duration));
458
459    // Change to None
460    let result = atomic_duration.compare_exchange(
461      Some(initial_duration),
462      None,
463      Ordering::SeqCst,
464      Ordering::SeqCst,
465    );
466    assert!(result.is_ok());
467    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
468
469    // Change back to Some(Duration)
470    let new_duration = Duration::from_secs(5);
471    let result = atomic_duration.compare_exchange(
472      None,
473      Some(new_duration),
474      Ordering::SeqCst,
475      Ordering::SeqCst,
476    );
477    assert!(result.is_ok());
478    assert_eq!(atomic_duration.load(Ordering::SeqCst), Some(new_duration));
479  }
480
481  #[test]
482  #[cfg(feature = "std")]
483  fn test_atomic_option_duration_thread_safety() {
484    use std::sync::Arc;
485    use std::thread;
486
487    let atomic_duration = Arc::new(AtomicOptionDuration::new(Some(Duration::from_secs(0))));
488    let mut handles = vec![];
489
490    // Spawn multiple threads to increment the duration
491    for _ in 0..10 {
492      let atomic_clone = Arc::clone(&atomic_duration);
493      let handle = thread::spawn(move || {
494        for _ in 0..100 {
495          loop {
496            let current = atomic_clone.load(Ordering::SeqCst);
497            let new_duration = current
498              .map(|d| d + Duration::from_millis(1))
499              .or(Some(Duration::from_millis(1)));
500            match atomic_clone.compare_exchange_weak(
501              current,
502              new_duration,
503              Ordering::SeqCst,
504              Ordering::SeqCst,
505            ) {
506              Ok(_) => break,     // Successfully updated
507              Err(_) => continue, // Spurious failure, retry
508            }
509          }
510        }
511      });
512      handles.push(handle);
513    }
514
515    // Wait for all threads to complete
516    for handle in handles {
517      handle.join().unwrap();
518    }
519
520    // Verify the final value
521    let expected_duration = Some(Duration::from_millis(10 * 100));
522    assert_eq!(atomic_duration.load(Ordering::SeqCst), expected_duration);
523  }
524
525  #[cfg(feature = "std")]
526  #[test]
527  fn test_atomic_option_duration_debug() {
528    let atomic_duration = AtomicOptionDuration::new(Some(Duration::from_secs(1)));
529    let debug_str = format!("{:?}", atomic_duration);
530    assert!(debug_str.contains("AtomicOptionDuration"));
531  }
532
533  #[cfg(feature = "std")]
534  #[test]
535  fn test_atomic_option_duration_debug_none() {
536    let atomic_duration = AtomicOptionDuration::none();
537    let debug_str = format!("{:?}", atomic_duration);
538    assert!(debug_str.contains("AtomicOptionDuration"));
539  }
540
541  #[test]
542  fn test_atomic_option_duration_default() {
543    let atomic_duration = AtomicOptionDuration::default();
544    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
545  }
546
547  #[test]
548  fn test_atomic_option_duration_from() {
549    let duration = Some(Duration::from_secs(42));
550    let atomic_duration = AtomicOptionDuration::from(duration);
551    assert_eq!(atomic_duration.load(Ordering::SeqCst), duration);
552  }
553
554  #[test]
555  fn test_atomic_option_duration_from_none() {
556    let atomic_duration = AtomicOptionDuration::from(None);
557    assert_eq!(atomic_duration.load(Ordering::SeqCst), None);
558  }
559
560  #[test]
561  fn test_atomic_option_duration_into_inner() {
562    let duration = Some(Duration::from_secs(3));
563    let atomic_duration = AtomicOptionDuration::new(duration);
564    assert_eq!(atomic_duration.into_inner(), duration);
565  }
566
567  #[test]
568  fn test_atomic_option_duration_into_inner_none() {
569    let atomic_duration = AtomicOptionDuration::none();
570    assert_eq!(atomic_duration.into_inner(), None);
571  }
572
573  #[test]
574  fn test_atomic_option_duration_fetch_update() {
575    let initial = Some(Duration::from_secs(4));
576    let atomic_duration = AtomicOptionDuration::new(initial);
577
578    let result = atomic_duration.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |d| {
579      Some(d.map(|val| val + Duration::from_secs(2)))
580    });
581    assert_eq!(result, Ok(initial));
582    assert_eq!(
583      atomic_duration.load(Ordering::SeqCst),
584      Some(Duration::from_secs(6))
585    );
586
587    // fetch_update returning None should fail
588    let result = atomic_duration.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None);
589    assert!(result.is_err());
590  }
591
592  #[cfg(feature = "serde")]
593  #[test]
594  fn test_atomic_option_duration_serde() {
595    use serde::{Deserialize, Serialize};
596
597    #[derive(Serialize, Deserialize)]
598    struct Test {
599      duration: AtomicOptionDuration,
600    }
601
602    let test = Test {
603      duration: AtomicOptionDuration::new(Some(Duration::from_secs(5))),
604    };
605    let serialized = serde_json::to_string(&test).unwrap();
606    let deserialized: Test = serde_json::from_str(&serialized).unwrap();
607    assert_eq!(
608      deserialized.duration.load(Ordering::SeqCst),
609      Some(Duration::from_secs(5))
610    );
611
612    let test = Test {
613      duration: AtomicOptionDuration::new(None),
614    };
615    let serialized = serde_json::to_string(&test).unwrap();
616    let deserialized: Test = serde_json::from_str(&serialized).unwrap();
617    assert_eq!(deserialized.duration.load(Ordering::SeqCst), None);
618  }
619
620  #[test]
621  fn decode_option_duration_roundtrip() {
622    let cases: [Option<Duration>; 4] = [
623      None,
624      Some(Duration::ZERO),
625      Some(Duration::from_secs(1)),
626      Some(Duration::new(123_456_789, 999_999_999)),
627    ];
628    for d in cases {
629      assert_eq!(decode_option_duration(encode_option_duration(d)), d);
630    }
631  }
632
633  #[test]
634  fn decode_option_duration_saturates_on_non_canonical_input() {
635    // u128::MAX has bit 127 set (= Some), nanos = u32::MAX (> 1e9),
636    // and the extracted seconds = u64::MAX. The old implementation
637    // panicked; the new one saturates.
638    let max = decode_option_duration(u128::MAX);
639    assert_eq!(max, Some(Duration::new(u64::MAX, 999_999_999)));
640
641    // Zero is the None sentinel — verify it stays None even when all
642    // lower bits are clear.
643    assert_eq!(decode_option_duration(0), None);
644
645    // Bit 127 alone = Some with seconds=0, nanos=0.
646    assert_eq!(decode_option_duration(1u128 << 127), Some(Duration::ZERO));
647  }
648}