orengine_utils/backoff.rs
1//! This module provides a [`Backoff`] that can be used to busy-wait with
2//! preemptive yield when it is necessary.
3//!
4//! It has the same API as `crossbeam::Backoff`.
5use crate::hints::likely;
6use core::cell::Cell;
7use core::fmt;
8
9const SPIN_LIMIT: u32 = 6;
10
11/// Performs exponential backoff in spin loops.
12///
13/// Backing off in spin loops reduces contention and improves overall performance.
14///
15/// This primitive can execute *YIELD* and *PAUSE* instructions, yield the current thread to the OS
16/// scheduler, and tell when it is a good time to block the thread using a different synchronization
17/// mechanism. Each step of the back off procedure takes roughly twice as long as the previous
18/// step.
19pub struct Backoff {
20 step: Cell<u32>,
21}
22
23impl Backoff {
24 /// Creates a new `Backoff` instance.
25 #[inline]
26 pub fn new() -> Self {
27 Self { step: Cell::new(0) }
28 }
29
30 /// Returns the current backoff step
31 /// (how many times it has been called since the last [`reset`](Self::reset)).
32 #[inline]
33 pub fn step(&self) -> u32 {
34 self.step.get()
35 }
36
37 /// Resets the backoff state.
38 #[inline]
39 pub fn reset(&self) {
40 self.step.set(0);
41 }
42
43 /// Backs off in a lock-free loop.
44 ///
45 /// This method should be used when we need to retry an operation because another thread made
46 /// progress.
47 ///
48 /// The processor may yield using the *YIELD* or *PAUSE* instruction.
49 #[inline]
50 pub fn spin(&self) {
51 for _ in 0..1 << self.step.get().min(SPIN_LIMIT) {
52 std::hint::spin_loop();
53 }
54
55 self.step.set(self.step.get() + 1);
56 }
57
58 /// It [`spins`](Self::spin) or calls the provided function if
59 /// it [`should be used`](Self::is_completed).
60 ///
61 /// If the provided function is [`std::thread::yield_now`],
62 /// then this function has the same behaviour as [`snooze`](Self::snooze).
63 #[inline]
64 pub fn spin_or<F>(&self, f: F)
65 where
66 F: FnOnce(),
67 {
68 if likely(self.step.get() < SPIN_LIMIT) {
69 for _ in 0..1 << self.step.get().min(SPIN_LIMIT) {
70 std::hint::spin_loop();
71 }
72 } else {
73 f();
74 }
75
76 self.step.set(self.step.get() + 1);
77 }
78
79 /// Backs off in a blocking loop.
80 ///
81 /// This method should be used when we need to wait for another thread to make progress.
82 ///
83 /// The processor may yield using the *YIELD* or *PAUSE* instruction, and the current thread
84 /// may yield by giving up a timeslice to the OS scheduler.
85 ///
86 /// In `#[no_std]` environments, this method is equivalent to [`spin`].
87 ///
88 /// If possible, use [`is_completed`] to check when it is advised to stop using backoff and
89 /// block the current thread using a different synchronization mechanism instead.
90 ///
91 /// [`spin`]: Backoff::spin
92 /// [`is_completed`]: Backoff::is_completed
93 #[inline]
94 pub fn snooze(&self) {
95 self.spin_or(std::thread::yield_now);
96 }
97
98 /// Returns `true` if exponential backoff has completed and blocking the thread is advised.
99 #[inline]
100 pub fn is_completed(&self) -> bool {
101 self.step.get() >= SPIN_LIMIT
102 }
103}
104
105impl fmt::Debug for Backoff {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 f.debug_struct("Backoff")
108 .field("step", &self.step)
109 .field("is_completed", &self.is_completed())
110 .finish()
111 }
112}
113
114impl Default for Backoff {
115 fn default() -> Self {
116 Self::new()
117 }
118}