Skip to main content

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}