omango_util/
backoff.rs

1// Copyright (c) 2024 Trung Tran <tqtrungse@gmail.com>
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! This file was modified based on the following program in Crossbeam-Utils.
22//!
23//! Source: `<https://github.com/crossbeam-rs/crossbeam/blob/master/crossbeam-utils/src/backoff.rs>`
24//!
25//! Copyright & License:
26//!   - The Crossbeam Project Developers
27//!   - The MIT License (MIT) or Apache License 2.0
28//!         `<https://opensource.org/licenses/MIT>`
29//!         '<https://www.apache.org/licenses/LICENSE-2.0>'
30
31use core::cell::Cell;
32
33use crate::hint::likely;
34
35const SPIN_LIMIT: u32 = 6;
36const YIELD_LIMIT: u32 = 10;
37
38/// Makes the current thread to wait in the short time.
39///
40/// It should be used to implement wait-retry in high contention multithreading environment
41/// because of improving performance significantly.
42///
43/// It should not be used to replace other blocking mechanism.
44pub struct Backoff {
45    step: Cell<u32>,
46}
47
48impl Backoff {
49    #[inline(always)]
50    pub fn reset(&self) {
51        self.step.set(0);
52    }
53
54    /// Backs off in a lock-free loop.
55    ///
56    /// This method should be used when we need to retry an operation because another thread made
57    /// progress.
58    ///
59    /// The processor may yield using the *YIELD* or *PAUSE* instruction.
60    #[inline]
61    pub fn spin(&self) {
62        for _ in 0..1 << self.step.get().min(SPIN_LIMIT) {
63            core::hint::spin_loop();
64        }
65        if self.step.get() <= SPIN_LIMIT {
66            self.step.set(self.step.get() + 1);
67        }
68    }
69
70    /// Backs off in a blocking loop.
71    ///
72    /// This method should be used when we need to wait for another thread to make progress.
73    ///
74    /// The processor may yield using the *YIELD* or *PAUSE* instruction and the current thread
75    /// may yield by giving up a time slice to the OS scheduler.
76    #[inline]
77    pub fn snooze(&self) {
78        if self.step.get() <= SPIN_LIMIT {
79            for _ in 0..1 << self.step.get() {
80                core::hint::spin_loop();
81            }
82            self.step.set(self.step.get() + 1);
83        } else {
84            std::thread::yield_now();
85        }
86    }
87
88    /// Backs off in a blocking loop.
89    ///
90    /// This method should be used when we need to wait for another thread to make progress.
91    ///
92    /// The processor may yield using the *YIELD* or *PAUSE* instruction and the current thread
93    /// may yield by giving up a time slice to the OS scheduler.
94    ///
95    /// Return `true` to advise to stop using backoff and
96    /// block the current thread using a different synchronization mechanism instead.
97    #[inline]
98    pub fn snooze_completed(&self) -> bool {
99        if likely(self.step.get() <= SPIN_LIMIT) {
100            for _ in 0..1 << self.step.get() {
101                core::hint::spin_loop();
102            }
103        } else {
104            std::thread::yield_now();
105        }
106
107        if likely(self.step.get() <= YIELD_LIMIT) {
108            self.step.set(self.step.get() + 1);
109            return false;
110        }
111        true
112    }
113}
114
115impl Default for Backoff {
116    #[inline(always)]
117    fn default() -> Backoff {
118        Self {
119            step: Cell::new(0)
120        }
121    }
122}