indexbus_core/
wait.rs

1use crate::cpu_relax;
2
3/// Progressive backoff when polling.
4///
5/// This trait is intentionally minimal and `no_std`-friendly.
6/// It is typically used at the edges (router loops, receive loops) to avoid busy-spinning.
7///
8/// ## Contract
9///
10/// - Implementations should be cheap and panic-free.
11/// - `wait` may spin, yield, or sleep depending on the strategy.
12/// - Callers should treat `wait` as a hint; it does not guarantee that the observed condition
13///   will change.
14pub trait WaitStrategy {
15    /// Reset internal counters to the initial (most aggressive) state.
16    fn reset(&mut self);
17
18    /// Perform one backoff step (spin/yield/sleep depending on implementation).
19    fn wait(&mut self);
20}
21
22/// no_std-friendly spin-only wait strategy.
23#[derive(Debug, Clone)]
24pub struct SpinWait {
25    spins: u32,
26    spin_limit: u32,
27}
28
29impl Default for SpinWait {
30    fn default() -> Self {
31        Self {
32            spins: 0,
33            spin_limit: 10_000,
34        }
35    }
36}
37
38impl SpinWait {
39    /// Create a spin-only wait strategy.
40    ///
41    /// `spin_limit` caps the number of calls to `wait` that will increment the internal spin
42    /// counter before it saturates.
43    pub const fn new(spin_limit: u32) -> Self {
44        Self {
45            spins: 0,
46            spin_limit,
47        }
48    }
49}
50
51impl WaitStrategy for SpinWait {
52    #[inline]
53    fn reset(&mut self) {
54        self.spins = 0;
55    }
56
57    #[inline]
58    fn wait(&mut self) {
59        cpu_relax();
60        if self.spins < self.spin_limit {
61            self.spins += 1;
62        }
63    }
64}
65
66/// std-only: spin, then yield, then sleep with capped exponential backoff.
67#[cfg(feature = "std")]
68#[derive(Debug, Clone)]
69pub struct StdBackoff {
70    spins: u32,
71    yields: u32,
72    sleep_us: u64,
73
74    spin_limit: u32,
75    yield_limit: u32,
76    sleep_us_initial: u64,
77    sleep_us_max: u64,
78}
79
80/// std-only: cooperative yield-only wait strategy.
81#[cfg(feature = "std")]
82#[derive(Debug, Clone, Default)]
83pub struct YieldWait;
84
85#[cfg(feature = "std")]
86impl YieldWait {
87    /// Create a yield-only wait strategy.
88    pub const fn new() -> Self {
89        Self
90    }
91}
92
93#[cfg(feature = "std")]
94impl WaitStrategy for YieldWait {
95    #[inline]
96    fn reset(&mut self) {}
97
98    #[inline]
99    fn wait(&mut self) {
100        std::thread::yield_now();
101    }
102}
103
104#[cfg(feature = "std")]
105impl StdBackoff {
106    #[inline]
107    /// Create a backoff strategy that spins, then yields, then sleeps.
108    ///
109    /// Sleep duration grows up to `sleep_us_max` (best-effort) after exhausting spins/yields.
110    pub const fn new(
111        spin_limit: u32,
112        yield_limit: u32,
113        sleep_us_initial: u64,
114        sleep_us_max: u64,
115    ) -> Self {
116        Self {
117            spins: 0,
118            yields: 0,
119            sleep_us: 0,
120            spin_limit,
121            yield_limit,
122            sleep_us_initial,
123            sleep_us_max,
124        }
125    }
126}
127
128#[cfg(feature = "std")]
129impl Default for StdBackoff {
130    fn default() -> Self {
131        Self {
132            spins: 0,
133            yields: 0,
134            sleep_us: 0,
135            spin_limit: 10_000,
136            yield_limit: 1_000,
137            sleep_us_initial: 10,
138            sleep_us_max: 1_000,
139        }
140    }
141}
142
143#[cfg(feature = "std")]
144impl WaitStrategy for StdBackoff {
145    #[inline]
146    fn reset(&mut self) {
147        self.spins = 0;
148        self.yields = 0;
149        self.sleep_us = 0;
150    }
151
152    #[inline]
153    fn wait(&mut self) {
154        if self.spins < self.spin_limit {
155            self.spins += 1;
156            cpu_relax();
157            return;
158        }
159
160        if self.yields < self.yield_limit {
161            self.yields += 1;
162            std::thread::yield_now();
163            return;
164        }
165
166        if self.sleep_us == 0 {
167            self.sleep_us = self.sleep_us_initial;
168        } else {
169            self.sleep_us = (self.sleep_us * 2).min(self.sleep_us_max);
170        }
171
172        std::thread::sleep(std::time::Duration::from_micros(self.sleep_us));
173    }
174}