1 use crate::primitive::hint;
2 use core::cell::Cell;
3 use core::fmt;
4 
5 const SPIN_LIMIT: u32 = 6;
6 const YIELD_LIMIT: u32 = 10;
7 
8 /// Performs exponential backoff in spin loops.
9 ///
10 /// Backing off in spin loops reduces contention and improves overall performance.
11 ///
12 /// This primitive can execute *YIELD* and *PAUSE* instructions, yield the current thread to the OS
13 /// scheduler, and tell when is a good time to block the thread using a different synchronization
14 /// mechanism. Each step of the back off procedure takes roughly twice as long as the previous
15 /// step.
16 ///
17 /// # Examples
18 ///
19 /// Backing off in a lock-free loop:
20 ///
21 /// ```
22 /// use crossbeam_utils::Backoff;
23 /// use std::sync::atomic::AtomicUsize;
24 /// use std::sync::atomic::Ordering::SeqCst;
25 ///
26 /// fn fetch_mul(a: &AtomicUsize, b: usize) -> usize {
27 ///     let backoff = Backoff::new();
28 ///     loop {
29 ///         let val = a.load(SeqCst);
30 ///         if a.compare_exchange(val, val.wrapping_mul(b), SeqCst, SeqCst).is_ok() {
31 ///             return val;
32 ///         }
33 ///         backoff.spin();
34 ///     }
35 /// }
36 /// ```
37 ///
38 /// Waiting for an [`AtomicBool`] to become `true`:
39 ///
40 /// ```
41 /// use crossbeam_utils::Backoff;
42 /// use std::sync::atomic::AtomicBool;
43 /// use std::sync::atomic::Ordering::SeqCst;
44 ///
45 /// fn spin_wait(ready: &AtomicBool) {
46 ///     let backoff = Backoff::new();
47 ///     while !ready.load(SeqCst) {
48 ///         backoff.snooze();
49 ///     }
50 /// }
51 /// ```
52 ///
53 /// Waiting for an [`AtomicBool`] to become `true` and parking the thread after a long wait.
54 /// Note that whoever sets the atomic variable to `true` must notify the parked thread by calling
55 /// [`unpark()`]:
56 ///
57 /// ```
58 /// use crossbeam_utils::Backoff;
59 /// use std::sync::atomic::AtomicBool;
60 /// use std::sync::atomic::Ordering::SeqCst;
61 /// use std::thread;
62 ///
63 /// fn blocking_wait(ready: &AtomicBool) {
64 ///     let backoff = Backoff::new();
65 ///     while !ready.load(SeqCst) {
66 ///         if backoff.is_completed() {
67 ///             thread::park();
68 ///         } else {
69 ///             backoff.snooze();
70 ///         }
71 ///     }
72 /// }
73 /// ```
74 ///
75 /// [`is_completed`]: Backoff::is_completed
76 /// [`std::thread::park()`]: std::thread::park
77 /// [`Condvar`]: std::sync::Condvar
78 /// [`AtomicBool`]: std::sync::atomic::AtomicBool
79 /// [`unpark()`]: std::thread::Thread::unpark
80 pub struct Backoff {
81     step: Cell<u32>,
82 }
83 
84 impl Backoff {
85     /// Creates a new `Backoff`.
86     ///
87     /// # Examples
88     ///
89     /// ```
90     /// use crossbeam_utils::Backoff;
91     ///
92     /// let backoff = Backoff::new();
93     /// ```
94     #[inline]
new() -> Self95     pub fn new() -> Self {
96         Backoff { step: Cell::new(0) }
97     }
98 
99     /// Resets the `Backoff`.
100     ///
101     /// # Examples
102     ///
103     /// ```
104     /// use crossbeam_utils::Backoff;
105     ///
106     /// let backoff = Backoff::new();
107     /// backoff.reset();
108     /// ```
109     #[inline]
reset(&self)110     pub fn reset(&self) {
111         self.step.set(0);
112     }
113 
114     /// Backs off in a lock-free loop.
115     ///
116     /// This method should be used when we need to retry an operation because another thread made
117     /// progress.
118     ///
119     /// The processor may yield using the *YIELD* or *PAUSE* instruction.
120     ///
121     /// # Examples
122     ///
123     /// Backing off in a lock-free loop:
124     ///
125     /// ```
126     /// use crossbeam_utils::Backoff;
127     /// use std::sync::atomic::AtomicUsize;
128     /// use std::sync::atomic::Ordering::SeqCst;
129     ///
130     /// fn fetch_mul(a: &AtomicUsize, b: usize) -> usize {
131     ///     let backoff = Backoff::new();
132     ///     loop {
133     ///         let val = a.load(SeqCst);
134     ///         if a.compare_exchange(val, val.wrapping_mul(b), SeqCst, SeqCst).is_ok() {
135     ///             return val;
136     ///         }
137     ///         backoff.spin();
138     ///     }
139     /// }
140     ///
141     /// let a = AtomicUsize::new(7);
142     /// assert_eq!(fetch_mul(&a, 8), 7);
143     /// assert_eq!(a.load(SeqCst), 56);
144     /// ```
145     #[inline]
spin(&self)146     pub fn spin(&self) {
147         for _ in 0..1 << self.step.get().min(SPIN_LIMIT) {
148             hint::spin_loop();
149         }
150 
151         if self.step.get() <= SPIN_LIMIT {
152             self.step.set(self.step.get() + 1);
153         }
154     }
155 
156     /// Backs off in a blocking loop.
157     ///
158     /// This method should be used when we need to wait for another thread to make progress.
159     ///
160     /// The processor may yield using the *YIELD* or *PAUSE* instruction and the current thread
161     /// may yield by giving up a timeslice to the OS scheduler.
162     ///
163     /// In `#[no_std]` environments, this method is equivalent to [`spin`].
164     ///
165     /// If possible, use [`is_completed`] to check when it is advised to stop using backoff and
166     /// block the current thread using a different synchronization mechanism instead.
167     ///
168     /// [`spin`]: Backoff::spin
169     /// [`is_completed`]: Backoff::is_completed
170     ///
171     /// # Examples
172     ///
173     /// Waiting for an [`AtomicBool`] to become `true`:
174     ///
175     /// ```
176     /// use crossbeam_utils::Backoff;
177     /// use std::sync::Arc;
178     /// use std::sync::atomic::AtomicBool;
179     /// use std::sync::atomic::Ordering::SeqCst;
180     /// use std::thread;
181     /// use std::time::Duration;
182     ///
183     /// fn spin_wait(ready: &AtomicBool) {
184     ///     let backoff = Backoff::new();
185     ///     while !ready.load(SeqCst) {
186     ///         backoff.snooze();
187     ///     }
188     /// }
189     ///
190     /// let ready = Arc::new(AtomicBool::new(false));
191     /// let ready2 = ready.clone();
192     ///
193     /// thread::spawn(move || {
194     ///     thread::sleep(Duration::from_millis(100));
195     ///     ready2.store(true, SeqCst);
196     /// });
197     ///
198     /// assert_eq!(ready.load(SeqCst), false);
199     /// spin_wait(&ready);
200     /// assert_eq!(ready.load(SeqCst), true);
201     /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371
202     /// ```
203     ///
204     /// [`AtomicBool`]: std::sync::atomic::AtomicBool
205     #[inline]
snooze(&self)206     pub fn snooze(&self) {
207         if self.step.get() <= SPIN_LIMIT {
208             for _ in 0..1 << self.step.get() {
209                 hint::spin_loop();
210             }
211         } else {
212             #[cfg(not(feature = "std"))]
213             for _ in 0..1 << self.step.get() {
214                 hint::spin_loop();
215             }
216 
217             #[cfg(feature = "std")]
218             ::std::thread::yield_now();
219         }
220 
221         if self.step.get() <= YIELD_LIMIT {
222             self.step.set(self.step.get() + 1);
223         }
224     }
225 
226     /// Returns `true` if exponential backoff has completed and blocking the thread is advised.
227     ///
228     /// # Examples
229     ///
230     /// Waiting for an [`AtomicBool`] to become `true` and parking the thread after a long wait:
231     ///
232     /// ```
233     /// use crossbeam_utils::Backoff;
234     /// use std::sync::Arc;
235     /// use std::sync::atomic::AtomicBool;
236     /// use std::sync::atomic::Ordering::SeqCst;
237     /// use std::thread;
238     /// use std::time::Duration;
239     ///
240     /// fn blocking_wait(ready: &AtomicBool) {
241     ///     let backoff = Backoff::new();
242     ///     while !ready.load(SeqCst) {
243     ///         if backoff.is_completed() {
244     ///             thread::park();
245     ///         } else {
246     ///             backoff.snooze();
247     ///         }
248     ///     }
249     /// }
250     ///
251     /// let ready = Arc::new(AtomicBool::new(false));
252     /// let ready2 = ready.clone();
253     /// let waiter = thread::current();
254     ///
255     /// thread::spawn(move || {
256     ///     thread::sleep(Duration::from_millis(100));
257     ///     ready2.store(true, SeqCst);
258     ///     waiter.unpark();
259     /// });
260     ///
261     /// assert_eq!(ready.load(SeqCst), false);
262     /// blocking_wait(&ready);
263     /// assert_eq!(ready.load(SeqCst), true);
264     /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371
265     /// ```
266     ///
267     /// [`AtomicBool`]: std::sync::atomic::AtomicBool
268     #[inline]
is_completed(&self) -> bool269     pub fn is_completed(&self) -> bool {
270         self.step.get() > YIELD_LIMIT
271     }
272 }
273 
274 impl fmt::Debug for Backoff {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result275     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276         f.debug_struct("Backoff")
277             .field("step", &self.step)
278             .field("is_completed", &self.is_completed())
279             .finish()
280     }
281 }
282 
283 impl Default for Backoff {
default() -> Backoff284     fn default() -> Backoff {
285         Backoff::new()
286     }
287 }
288