atomic_backoff/lib.rs
1//! # atomic-backoff
2//!
3//! Customizable backoff strategies for compare-and-swap loops and spin loops.
4//!
5//! Compare-and-swap (CAS) loops and spin loops can often be optimized by adding backoff at each
6//! iteration, i.e. waiting a bit before the next iteration, in order to reduce the contention on
7//! the CPU's cache lines.
8//!
9//! As the optimal backoff strategy depends on multiple factors, especially the expected
10//! contention, this crate provides a generic [`BackoffStrategy`] to help customize algorithms
11//! using CAS/spin loops. Typical backoff strategies like [`ExponentialBackoff`] are also provided.
12//!
13//! Atomic types are extended with [`try_update_with_backoff`]/[`update_with_backoff`] methods,
14//! mirroring their std `try_update`/`update` counterparts.
15//!
16//! For handwritten CAS loops, see [`BackoffStrategy::backoff_reload`] and [`BackoffState`];
17//! for spin loops, see [`BackoffStrategy::backoff_until`], or [`BoundedBackoffStrategy`] to spin
18//! a bounded number of iterations before falling back to a slower waiting mechanism.
19//!
20//! # Examples
21//!
22//! ```rust
23//! use std::{
24//! sync::atomic::{AtomicUsize, Ordering::Relaxed},
25//! thread,
26//! time::{Duration, Instant},
27//! };
28//!
29//! use atomic_backoff::{AtomicWithBackoffExt, BackoffStrategy, ExponentialBackoff, NoBackoff};
30//!
31//! fn parallel_increment<S: BackoffStrategy>(threads: usize, iterations: usize) -> Duration {
32//! let counter = AtomicUsize::new(0);
33//! let start = Instant::now();
34//! thread::scope(|s| {
35//! for _ in 0..threads {
36//! s.spawn(|| {
37//! for _ in 0..iterations {
38//! counter.update_with_backoff(Relaxed, Relaxed, |x| x + 1, S::default());
39//! }
40//! });
41//! }
42//! });
43//! assert_eq!(counter.load(Relaxed), threads * iterations);
44//! start.elapsed()
45//! }
46//!
47//! let no_backoff = parallel_increment::<NoBackoff>(4, 10_000);
48//! let exponential = parallel_increment::<ExponentialBackoff<6, 4>>(4, 10_000);
49//! println!("no backoff: {no_backoff:?}, exponential backoff: {exponential:?}");
50//! // no backoff: 2.08ms, exponential backoff: 646µs
51//! ```
52//!
53//! [`try_update_with_backoff`]: AtomicWithBackoffExt::try_update_with_backoff
54//! [`update_with_backoff`]: AtomicWithBackoffExt::update_with_backoff
55#![no_std]
56
57#[cfg(feature = "std")]
58extern crate std;
59
60use core::{hint::spin_loop, sync::atomic::Ordering};
61
62/// Backoff strategy to be used after an atomic compare-and-swap (CAS) failure and in spin loops.
63///
64/// Waiting before retrying a failed CAS can greatly reduce the contention on the atomic's cache
65/// line, and improve the performance of CAS loops.
66///
67/// Spin loops also benefit from backoff as it avoids keeping the CPU 100% busy while waiting,
68/// at little latency cost.
69pub trait BackoffStrategy: Default + Send + Sync + 'static {
70 /// Whether the strategy does backoff or not.
71 ///
72 /// Some algorithms may have a different behavior depending on whether backoff is used; for
73 /// example switching between an unbounded spin loop or a thread parking algorithm. This
74 /// constant can be used for this purpose.
75 ///
76 /// It should be set to `false` only for [`NoBackoff`].
77 const BACKOFF: bool = true;
78
79 /// Performs backoff and returns how the CAS should be retried.
80 ///
81 /// [`will_reload`](Self::will_reload) should also be implemented accordingly.
82 ///
83 /// In spin loops, the returned value can simply be ignored.
84 fn backoff(&mut self) -> RetryStrategy;
85
86 /// Returns `true` if the next call to [`backoff`](Self::backoff) will not return
87 /// [`RetryStrategy::NoReload`].
88 ///
89 /// This hint can be used to downgrade the failure ordering of the CAS to `Relaxed` when the
90 /// returned value is overwritten anyway.
91 #[inline]
92 fn will_reload(&self) -> bool {
93 false
94 }
95
96 /// Performs backoff after a failed CAS and reloads the atomic value according to the returned
97 /// [`RetryStrategy`].
98 ///
99 /// [`ReloadUntilUnchanged`] causes this function to loop until the value stops changing. If the
100 /// value returned by the failed CAS must be tested between reloads, use [`BackoffState`]
101 /// instead.
102 ///
103 /// # Example
104 ///
105 /// ```rust
106 /// # use core::sync::atomic::{AtomicUsize, Ordering};
107 /// # use atomic_backoff::BackoffStrategy;
108 /// #
109 /// fn update_with_backoff(
110 /// atomic: &AtomicUsize,
111 /// set_order: Ordering,
112 /// fetch_order: Ordering,
113 /// mut f: impl FnMut(usize) -> usize,
114 /// mut strategy: impl BackoffStrategy,
115 /// ) -> usize {
116 /// let mut current = atomic.load(fetch_order);
117 /// loop {
118 /// let failure_order = if strategy.will_reload() {
119 /// Ordering::Relaxed
120 /// } else {
121 /// fetch_order
122 /// };
123 /// match atomic.compare_exchange_weak(current, f(current), set_order, failure_order) {
124 /// Ok(x) => return x,
125 /// Err(cur) => current = strategy.backoff_reload(cur, || atomic.load(fetch_order)),
126 /// }
127 /// }
128 /// }
129 /// ```
130 ///
131 /// [`ReloadUntilUnchanged`]: RetryStrategy::ReloadUntilUnchanged
132 #[inline]
133 fn backoff_reload<T: PartialEq, F: FnMut() -> T>(
134 &mut self,
135 mut current: T,
136 mut reload: F,
137 ) -> T {
138 loop {
139 match self.backoff() {
140 RetryStrategy::NoReload => return current,
141 RetryStrategy::Reload => return reload(),
142 RetryStrategy::ReloadUntilUnchanged => {
143 let reloaded = reload();
144 if reloaded == current {
145 return current;
146 }
147 current = reloaded;
148 }
149 }
150 }
151 }
152
153 /// Loops until a condition is satisfied, performing backoff at each iteration.
154 #[inline]
155 fn backoff_until<C: BackoffUntilCondition, F: FnMut() -> C>(&mut self, mut f: F) -> C::Result {
156 loop {
157 if let Some(res) = f().into_result() {
158 return res;
159 }
160 self.backoff();
161 }
162 }
163}
164
165/// Retry strategy of a failed atomic compare-and-swap (CAS).
166///
167/// It is returned by [`BackoffStrategy::backoff`] and tells what to do with the value returned
168/// by the failed CAS before retrying.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum RetryStrategy {
171 /// Retry with the value returned by the failed CAS.
172 NoReload,
173 /// Reload the atomic and retry the CAS with the up-to-date value.
174 Reload,
175 /// Reload the atomic and keep backing off while the value changes between reloads, then retry
176 /// with the up-to-date value.
177 ReloadUntilUnchanged,
178}
179
180/// A condition checked in [`BackoffStrategy::backoff_until`].
181///
182/// It should typically be a `bool` or an `Option<T>`.
183pub trait BackoffUntilCondition {
184 /// The result to return when the condition is satisfied.
185 type Result;
186 /// Converts the condition into a result to be returned.
187 fn into_result(self) -> Option<Self::Result>;
188}
189
190impl BackoffUntilCondition for bool {
191 type Result = ();
192 #[inline]
193 fn into_result(self) -> Option<Self::Result> {
194 if self { Some(()) } else { None }
195 }
196}
197
198impl<T> BackoffUntilCondition for Option<T> {
199 type Result = T;
200 #[inline]
201 fn into_result(self) -> Option<Self::Result> {
202 self
203 }
204}
205
206/// No backoff.
207///
208/// Retries immediately with the value returned by the failed CAS.
209#[derive(Debug, Default)]
210pub struct NoBackoff;
211
212impl BackoffStrategy for NoBackoff {
213 const BACKOFF: bool = false;
214 #[inline]
215 fn backoff(&mut self) -> RetryStrategy {
216 RetryStrategy::NoReload
217 }
218}
219
220impl BoundedBackoffStrategy for NoBackoff {
221 #[inline]
222 fn is_completed(&self) -> bool {
223 true
224 }
225}
226
227/// A [`BackoffStrategy`] which completes after a bounded number of iterations.
228///
229/// It is typically used to spin a bit before falling back to a slower waiting mechanism, like
230/// parking the thread.
231///
232/// # Examples
233///
234/// ```rust
235/// use std::sync::atomic::{AtomicBool, Ordering::Acquire};
236///
237/// use atomic_backoff::{BackoffLimit, BoundedBackoffStrategy, ExponentialBackoff};
238///
239/// fn wait(flag: &AtomicBool, park: impl Fn()) {
240/// let mut backoff = BackoffLimit::<ExponentialBackoff<6>, 10>::default();
241/// // Spin a bit, then park the thread if the flag is still not set.
242/// while backoff.try_backoff_until(|| flag.load(Acquire)).is_none() {
243/// park();
244/// }
245/// }
246/// ```
247pub trait BoundedBackoffStrategy: BackoffStrategy {
248 /// Returns `true` if the bounded number of iterations has been reached.
249 ///
250 /// [`backoff`](BackoffStrategy::backoff) can still be called afterward.
251 fn is_completed(&self) -> bool;
252
253 /// Loops until a condition is satisfied or the backoff is completed, performing backoff at
254 /// each iteration.
255 ///
256 /// Returns `None` if the backoff completed before the condition was satisfied.
257 #[inline]
258 fn try_backoff_until<C: BackoffUntilCondition, F: FnMut() -> C>(
259 &mut self,
260 mut f: F,
261 ) -> Option<C::Result> {
262 loop {
263 if let Some(res) = f().into_result() {
264 return Some(res);
265 }
266 if self.is_completed() {
267 return None;
268 }
269 self.backoff();
270 }
271 }
272}
273
274/// Wraps a [`BackoffStrategy`] to make it a [`BoundedBackoffStrategy`] completing after `LIMIT`
275/// iterations.
276#[derive(Debug, Default)]
277pub struct BackoffLimit<S, const LIMIT: usize> {
278 strategy: S,
279 iter: usize,
280}
281
282impl<S: BackoffStrategy, const LIMIT: usize> BackoffLimit<S, LIMIT> {
283 /// Wraps the given strategy.
284 pub fn new(strategy: S) -> Self {
285 Self { strategy, iter: 0 }
286 }
287}
288
289impl<S: BackoffStrategy, const LIMIT: usize> BackoffStrategy for BackoffLimit<S, LIMIT> {
290 const BACKOFF: bool = S::BACKOFF;
291
292 #[inline]
293 fn backoff(&mut self) -> RetryStrategy {
294 let retry = self.strategy.backoff();
295 self.iter = self.iter.saturating_add(1);
296 retry
297 }
298
299 #[inline]
300 fn will_reload(&self) -> bool {
301 self.strategy.will_reload()
302 }
303}
304
305impl<S: BackoffStrategy, const LIMIT: usize> BoundedBackoffStrategy for BackoffLimit<S, LIMIT> {
306 #[inline]
307 fn is_completed(&self) -> bool {
308 self.iter >= LIMIT
309 }
310}
311
312/// Emits a [`spin_loop`] and reloads the atomic value before retrying the CAS.
313#[derive(Debug, Default)]
314pub struct SpinBackoff;
315
316impl BackoffStrategy for SpinBackoff {
317 #[inline]
318 fn backoff(&mut self) -> RetryStrategy {
319 spin_loop();
320 RetryStrategy::Reload
321 }
322
323 #[inline]
324 fn will_reload(&self) -> bool {
325 true
326 }
327}
328
329/// Performs exponential backoff.
330///
331/// Each backoff iteration `iter` (starting from 0) calls [`spin_loop`] `1 << iter.min(SPIN_LIMIT)`
332/// times.
333///
334/// During the first `UNTIL_UNCHANGED_LIMIT` backoff iterations, backoff continues until the
335/// atomic's value stops changing between reloads; after that, the CAS is retried after a single
336/// reload to avoid starvation.
337///
338/// After `YIELD_AFTER` backoff iterations (and if the `std` feature is enabled), [`yield_now`] is
339/// called instead of spinning.
340///
341/// For reference, [`crossbeam::utils::Backoff`] is equivalent to `ExponentialBackoff<6>` with
342/// `Backoff::spin`, and `ExponentialBackoff<10, 0, 7>` with `Backoff::snooze`. However,
343/// `UNTIL_UNCHANGED_LIMIT` should also be used in contended CAS loop to further reduce contention.
344///
345/// [`yield_now`]: https://doc.rust-lang.org/std/thread/fn.yield_now.html
346/// [`crossbeam::utils::Backoff`]: https://docs.rs/crossbeam/latest/crossbeam/utils/struct.Backoff.html
347#[derive(Debug, Default)]
348pub struct ExponentialBackoff<
349 const SPIN_LIMIT: usize,
350 const UNTIL_UNCHANGED_LIMIT: usize = 0,
351 const YIELD_AFTER: usize = { usize::MAX },
352> {
353 iter: usize,
354}
355
356impl<const SPIN_LIMIT: usize, const UNTIL_UNCHANGED_LIMIT: usize, const YIELD_AFTER: usize>
357 ExponentialBackoff<SPIN_LIMIT, UNTIL_UNCHANGED_LIMIT, YIELD_AFTER>
358{
359 const ASSERT_SPIN_LIMIT: () = assert!(
360 SPIN_LIMIT < usize::BITS as usize,
361 "SPIN_LIMIT must be lower than usize::BITS"
362 );
363
364 /// Starts an exponential backoff at the given iteration (starting from 0).
365 ///
366 /// This constructor can be used to skip the first smaller iterations.
367 pub fn starts_at(iter: usize) -> Self {
368 ExponentialBackoff { iter }
369 }
370
371 /// Returns the current count of backoff iterations performed.
372 ///
373 /// It can be used for example to switch to another algorithm, like thread parking, after a
374 /// given iteration count.
375 pub fn iter_count(&self) -> usize {
376 self.iter
377 }
378}
379
380impl<const SPIN_LIMIT: usize, const UNTIL_UNCHANGED_LIMIT: usize, const YIELD_AFTER: usize>
381 BackoffStrategy for ExponentialBackoff<SPIN_LIMIT, UNTIL_UNCHANGED_LIMIT, YIELD_AFTER>
382{
383 #[inline]
384 fn backoff(&mut self) -> RetryStrategy {
385 let () = Self::ASSERT_SPIN_LIMIT;
386 if cfg!(feature = "std") && self.iter >= YIELD_AFTER {
387 #[cfg(feature = "std")]
388 std::thread::yield_now();
389 } else {
390 for _ in 0..1usize << self.iter.min(SPIN_LIMIT) {
391 spin_loop();
392 }
393 }
394 self.iter = self.iter.saturating_add(1);
395 if self.iter <= UNTIL_UNCHANGED_LIMIT {
396 RetryStrategy::ReloadUntilUnchanged
397 } else {
398 RetryStrategy::Reload
399 }
400 }
401
402 #[inline]
403 fn will_reload(&self) -> bool {
404 true
405 }
406}
407
408/// A wrapper around a [`BackoffStrategy`] to be used in CAS loops when the atomic value must be
409/// checked after each reload.
410///
411/// Contrary to [`BackoffStrategy::backoff_reload`], it allows checking for a termination condition
412/// and early exiting the loop before performing the backoff.
413///
414/// In order to avoid code duplication with the checks after the reloads,
415/// [`BackoffState::backoff_reload`] should be called at every iteration of the CAS loop before the
416/// CAS. However, to avoid performing a backoff before any CAS failure, `BackoffState` is
417/// initialized as disabled, and enabled after the first `backoff_reload` call.
418///
419/// # Examples
420///
421/// ```rust
422/// # use core::sync::atomic::{AtomicUsize, Ordering};
423/// # use atomic_backoff::{BackoffState, BackoffStrategy};
424/// #
425/// fn try_update_with_backoff(
426/// atomic: &AtomicUsize,
427/// set_order: Ordering,
428/// fetch_order: Ordering,
429/// mut f: impl FnMut(usize) -> Option<usize>,
430/// strategy: impl BackoffStrategy,
431/// ) -> Result<usize, usize> {
432/// let mut backoff = BackoffState::new(strategy);
433/// let mut current = atomic.load(fetch_order);
434/// loop {
435/// // Check the termination condition before backing off.
436/// let new = f(current).ok_or(current)?;
437/// // If the value has been reloaded, `new` must be recomputed.
438/// if backoff.backoff_reload(&mut current, || atomic.load(fetch_order)) {
439/// continue;
440/// }
441/// match atomic.compare_exchange_weak(current, new, set_order, fetch_order) {
442/// Ok(x) => return Ok(x),
443/// Err(cur) => current = cur,
444/// }
445/// }
446/// }
447/// ```
448#[derive(Debug, Default)]
449pub struct BackoffState<S> {
450 strategy: S,
451 enabled: bool,
452}
453
454impl<S: BackoffStrategy> BackoffState<S> {
455 /// Creates a new `BackoffState` with the given backoff strategy.
456 ///
457 /// The backoff starts as disabled so the first iteration before any CAS failure doesn't wait.
458 pub fn new(strategy: S) -> Self {
459 Self {
460 strategy,
461 enabled: false,
462 }
463 }
464
465 /// Starts the backoff in enabled mode.
466 ///
467 /// It is useful when the first CAS iteration is inlined in a hot function, and the complete CAS
468 /// loop with the backoff is outlined in a cold function, so the backoff must start enabled
469 /// after a CAS failure.
470 pub fn enable(mut self) -> Self {
471 self.enabled = true;
472 self
473 }
474
475 /// Enables the backoff for the next iteration or perform a backoff if already enabled.
476 ///
477 /// Returns `true` if the current atomic value has been updated after a reload, in which case
478 /// the new atomic value should be recomputed before retrying the CAS.
479 ///
480 /// The backoff can be temporarily disabled after a reload triggered by
481 /// [`RetryStrategy::Reload`] in order to execute the CAS with the reloaded value at the next
482 /// iteration.
483 #[inline]
484 pub fn backoff_reload<T: PartialEq, F: FnOnce() -> T>(
485 &mut self,
486 current: &mut T,
487 reload: F,
488 ) -> bool {
489 if !self.enabled {
490 self.enabled = true;
491 return false;
492 }
493 let retry = self.strategy.backoff();
494 if retry == RetryStrategy::NoReload {
495 return false;
496 }
497 let reloaded = reload();
498 if reloaded == *current {
499 return false;
500 }
501 *current = reloaded;
502 self.enabled = retry == RetryStrategy::ReloadUntilUnchanged;
503 true
504 }
505}
506
507impl<S: BackoffStrategy> From<S> for BackoffState<S> {
508 fn from(strategy: S) -> Self {
509 Self::new(strategy)
510 }
511}
512
513/// An atomic type.
514pub trait Atomic {
515 /// The value of the atomic.
516 type Value: Copy + PartialEq;
517 /// Load the value of the atomic.
518 fn load(&self, ordering: Ordering) -> Self::Value;
519 /// Stores a value into the atomic if the current value is the same as the `current` value.
520 fn compare_exchange_weak(
521 &self,
522 current: Self::Value,
523 new: Self::Value,
524 success: Ordering,
525 failure: Ordering,
526 ) -> Result<Self::Value, Self::Value>;
527}
528
529/// Extension trait providing CAS loop methods using a given [`BackoffStrategy`].
530pub trait AtomicWithBackoffExt: Atomic {
531 /// Fetches the value, and applies a function to it that returns an optional new value.
532 fn try_update_with_backoff<S, F>(
533 &self,
534 set_order: Ordering,
535 fetch_order: Ordering,
536 mut f: F,
537 strategy: S,
538 ) -> Result<Self::Value, Self::Value>
539 where
540 S: BackoffStrategy,
541 F: FnMut(Self::Value) -> Option<Self::Value>,
542 {
543 let mut backoff = BackoffState::new(strategy);
544 let mut current = self.load(fetch_order);
545 loop {
546 let new = f(current).ok_or(current)?;
547 if backoff.backoff_reload(&mut current, || self.load(fetch_order)) {
548 continue;
549 }
550 match self.compare_exchange_weak(current, new, set_order, fetch_order) {
551 Ok(x) => return Ok(x),
552 Err(cur) => current = cur,
553 }
554 }
555 }
556
557 /// Fetches the value, applies a function to it that returns a new value.
558 fn update_with_backoff<S, F>(
559 &self,
560 set_order: Ordering,
561 fetch_order: Ordering,
562 mut f: F,
563 mut strategy: S,
564 ) -> Self::Value
565 where
566 S: BackoffStrategy,
567 F: FnMut(Self::Value) -> Self::Value,
568 {
569 let mut current = self.load(fetch_order);
570 loop {
571 let failure_order = if strategy.will_reload() {
572 Ordering::Relaxed
573 } else {
574 fetch_order
575 };
576 match self.compare_exchange_weak(current, f(current), set_order, failure_order) {
577 Ok(x) => return x,
578 Err(cur) => current = strategy.backoff_reload(cur, || self.load(fetch_order)),
579 }
580 }
581 }
582}
583
584impl<T: Atomic> AtomicWithBackoffExt for T {}
585
586macro_rules! impl_atomic {
587 ($($($atomic:ident)::+ $(<$t:ident>)? => $value:ty,)*) => {$(
588 impl$(<$t>)? Atomic for $($atomic)::+$(<$t>)? {
589 type Value = $value;
590
591 #[inline(always)]
592 fn load(&self, ordering: Ordering) -> Self::Value {
593 self.load(ordering)
594 }
595
596 #[inline(always)]
597 fn compare_exchange_weak(
598 &self,
599 current: Self::Value,
600 new: Self::Value,
601 success: Ordering,
602 failure: Ordering,
603 ) -> Result<Self::Value, Self::Value> {
604 self.compare_exchange_weak(current, new, success, failure)
605 }
606 }
607 )*};
608}
609
610macro_rules! impl_core_atomic {
611 ($($size:literal: $atomic:ident $(<$t:ident>)? => $value:ty,)*) => {$(
612 #[cfg(target_has_atomic = $size)]
613 impl_atomic!(core::sync::atomic::$atomic $(<$t>)? => $value,);
614 )*};
615}
616
617impl_core_atomic! {
618 "8": AtomicBool => bool,
619 "8": AtomicI8 => i8,
620 "8": AtomicU8 => u8,
621 "16": AtomicI16 => i16,
622 "16": AtomicU16 => u16,
623 "32": AtomicI32 => i32,
624 "32": AtomicU32 => u32,
625 "64": AtomicI64 => i64,
626 "64": AtomicU64 => u64,
627 "ptr": AtomicIsize => isize,
628 "ptr": AtomicUsize => usize,
629 "ptr": AtomicPtr<T> => *mut T,
630}
631
632#[cfg(feature = "portable-atomic")]
633macro_rules! impl_portable_atomic {
634 ($($cfg:ident: $atomic:ident $(<$t:ident>)? => $value:ty,)*) => {
635 portable_atomic::cfg_has_atomic_cas! {$(
636 portable_atomic::$cfg! {
637 impl_atomic!(portable_atomic::$atomic $(<$t>)? => $value,);
638 }
639 )*}
640 };
641}
642
643#[cfg(feature = "portable-atomic")]
644impl_portable_atomic! {
645 cfg_has_atomic_8: AtomicBool => bool,
646 cfg_has_atomic_8: AtomicI8 => i8,
647 cfg_has_atomic_8: AtomicU8 => u8,
648 cfg_has_atomic_16: AtomicI16 => i16,
649 cfg_has_atomic_16: AtomicU16 => u16,
650 cfg_has_atomic_32: AtomicI32 => i32,
651 cfg_has_atomic_32: AtomicU32 => u32,
652 cfg_has_atomic_64: AtomicI64 => i64,
653 cfg_has_atomic_64: AtomicU64 => u64,
654 cfg_has_atomic_128: AtomicI128 => i128,
655 cfg_has_atomic_128: AtomicU128 => u128,
656 cfg_has_atomic_ptr: AtomicIsize => isize,
657 cfg_has_atomic_ptr: AtomicUsize => usize,
658 cfg_has_atomic_ptr: AtomicPtr<T> => *mut T,
659}
660
661// loom only provides 64-bit atomics on 64-bit targets.
662#[cfg(loom)]
663macro_rules! impl_loom_atomic {
664 ($($($width:literal:)? $atomic:ident $(<$t:ident>)? => $value:ty,)*) => {$(
665 $(#[cfg(target_pointer_width = $width)])?
666 impl_atomic!(loom::sync::atomic::$atomic $(<$t>)? => $value,);
667 )*};
668}
669
670#[cfg(loom)]
671impl_loom_atomic! {
672 AtomicBool => bool,
673 AtomicI8 => i8,
674 AtomicU8 => u8,
675 AtomicI16 => i16,
676 AtomicU16 => u16,
677 AtomicI32 => i32,
678 AtomicU32 => u32,
679 "64": AtomicI64 => i64,
680 "64": AtomicU64 => u64,
681 AtomicIsize => isize,
682 AtomicUsize => usize,
683 AtomicPtr<T> => *mut T,
684}
685
686#[cfg(test)]
687mod tests {
688 extern crate std;
689
690 use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
691 use std::{sync::Arc, thread};
692
693 use crate::{AtomicWithBackoffExt, BackoffLimit, BoundedBackoffStrategy, NoBackoff};
694
695 /// Spawns two threads incrementing the same atomic, initialized to 0,
696 /// and returns what each of them got.
697 fn increment_twice<R: Send + 'static>(
698 f: impl Fn(&AtomicUsize) -> R + Copy + Send + 'static,
699 ) -> [R; 2] {
700 let atomic = Arc::new(AtomicUsize::new(0));
701 let spawn = || {
702 let atomic = atomic.clone();
703 thread::spawn(move || f(&atomic))
704 };
705 let (t1, t2) = (spawn(), spawn());
706 [t1.join().unwrap(), t2.join().unwrap()]
707 }
708
709 #[test]
710 fn update() {
711 let results = increment_twice(|atomic| {
712 atomic.update_with_backoff(Relaxed, Relaxed, |x| x + 1, NoBackoff)
713 });
714 assert!(results == [0, 1] || results == [1, 0], "{:?}", results);
715 }
716
717 #[test]
718 fn try_update() {
719 let results = increment_twice(|atomic| {
720 let incr = |x| if x != 1 { Some(x + 1) } else { None };
721 atomic.try_update_with_backoff(Relaxed, Relaxed, incr, NoBackoff)
722 });
723 assert!(
724 results == [Ok(0), Err(1)] || results == [Err(1), Ok(0)],
725 "{:?}",
726 results
727 );
728 }
729
730 #[test]
731 fn backoff_limit() {
732 let mut backoff = BackoffLimit::<NoBackoff, 2>::default();
733 let mut calls = 0;
734 let res = backoff.try_backoff_until(|| {
735 calls += 1;
736 false
737 });
738 assert_eq!(res, None);
739 assert_eq!(calls, 3);
740 assert!(backoff.is_completed());
741 assert_eq!(backoff.try_backoff_until(|| Some(42)), Some(42));
742 assert!(NoBackoff.is_completed());
743 }
744}