maitake_sync/util/
backoff.rs

1/// An [exponential backoff] for spin loops.
2///
3/// This is a helper struct for spinning in a busy loop, with an exponentially
4/// increasing number of spins up to a maximum value.
5///
6/// [exponential backoff]: https://en.wikipedia.org/wiki/Exponential_backoff
7#[derive(Debug, Copy, Clone)]
8pub struct Backoff {
9    exp: u8,
10    max: u8,
11}
12
13// === impl Backoff ===
14
15impl Backoff {
16    /// The default maximum exponent (2^8).
17    ///
18    /// This is the maximum exponent returned by [`Backoff::new()`] and
19    /// [`Backoff::default()`]. To override the maximum exponent, use
20    /// [`Backoff::with_max_exponent()`].
21    pub const DEFAULT_MAX_EXPONENT: u8 = 8;
22
23    /// Returns a new exponential backoff with the maximum exponent set to
24    /// [`Self::DEFAULT_MAX_EXPONENT`].
25    #[must_use]
26    pub const fn new() -> Self {
27        Self {
28            exp: 0,
29            max: Self::DEFAULT_MAX_EXPONENT,
30        }
31    }
32
33    /// Returns a new exponential backoff with the provided max exponent.
34    #[must_use]
35    pub fn with_max_exponent(max: u8) -> Self {
36        assert!(max <= Self::DEFAULT_MAX_EXPONENT);
37        Self { exp: 0, max }
38    }
39
40    /// Backs off in a spin loop.
41    ///
42    /// This should be used when an operation needs to be retried because
43    /// another thread or core made progress. Depending on the target
44    /// architecture, this will generally issue a sequence of `yield` or `pause`
45    /// instructions.
46    ///
47    /// Each time this function is called, it will issue `2^exp` [spin loop
48    /// hints], where `exp` is the current exponent value (starting at 0). If
49    /// `exp` is less than the configured maximum exponent, the exponent is
50    /// incremented once the spin is complete.
51    #[inline(always)]
52    pub fn spin(&mut self) {
53        // Issue 2^exp pause instructions.
54        #[cfg_attr(loom, allow(unused_variables))]
55        let spins = 1 << self.exp;
56
57        #[cfg(not(loom))]
58        for _ in 0..spins {
59            crate::loom::hint::spin_loop();
60        }
61
62        #[cfg(loom)]
63        {
64            test_debug!("would back off for {spins} spins");
65            loom::thread::yield_now();
66        }
67
68        if self.exp < self.max {
69            self.exp += 1
70        }
71    }
72}
73
74impl Default for Backoff {
75    fn default() -> Self {
76        Self::new()
77    }
78}