crossfire_utils/
backoff.rs1use core::mem::transmute;
2use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
3
4pub const SPIN_LIMIT: u16 = 6;
5
6#[cfg(target_arch = "x86_64")]
7pub const DEFAULT_LIMIT: u16 = 6;
8#[cfg(not(target_arch = "x86_64"))]
9pub const DEFAULT_LIMIT: u16 = 10;
10pub const MAX_LIMIT: u16 = 10;
11
12pub const DEFAULT_CONFIG: u32 =
13 BackoffConfig { spin_limit: SPIN_LIMIT, limit: DEFAULT_LIMIT }.to_u32();
14
15static DETECT_CONFIG: AtomicU32 = AtomicU32::new(DEFAULT_CONFIG);
16
17static _INIT: AtomicBool = AtomicBool::new(false);
18
19pub fn detect_backoff_cfg() {
31 if _INIT.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed).is_err() {
32 return;
33 }
34 #[cfg(feature = "std")]
35 {
36 use core::num::NonZero;
37 if std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap())
38 == NonZero::new(1).unwrap()
39 {
40 DETECT_CONFIG.store(
42 BackoffConfig { spin_limit: 0, limit: DEFAULT_LIMIT }.to_u32(),
43 Ordering::Release,
44 );
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy)]
50#[repr(C)]
51pub struct BackoffConfig {
52 pub spin_limit: u16,
53 pub limit: u16,
54}
55
56impl Default for BackoffConfig {
57 #[inline(always)]
58 fn default() -> Self {
59 Self::from_u32(DEFAULT_CONFIG)
60 }
61}
62
63impl BackoffConfig {
64 #[inline(always)]
65 pub fn detect() -> Self {
66 Self::from_u32(DETECT_CONFIG.load(Ordering::Relaxed))
67 }
68
69 #[inline(always)]
70 pub const fn to_u32(self) -> u32 {
71 let i: u32 = unsafe { transmute(self) };
72 i
73 }
74
75 #[inline(always)]
76 pub const fn from_u32(config: u32) -> Self {
77 unsafe { transmute(config) }
78 }
79
80 #[allow(dead_code)]
81 #[inline(always)]
82 pub const fn async_limit(mut self, limit: u16) -> Self {
83 if limit < self.limit {
84 self.limit = limit;
85 }
86 self.spin_limit = limit;
87 self
88 }
89
90 #[allow(dead_code)]
91 #[inline(always)]
92 pub const fn limit(mut self, limit: u16) -> Self {
93 self.limit = limit;
94 self
95 }
96
97 #[allow(dead_code)]
98 #[inline(always)]
99 pub const fn spin(mut self, spin_limit: u16) -> Self {
100 if spin_limit < self.spin_limit {
101 self.spin_limit = spin_limit;
102 }
103 self
104 }
105}
106
107pub struct Backoff {
108 step: u16,
109 pub config: BackoffConfig,
110}
111
112impl Backoff {
113 #[inline(always)]
114 pub fn new() -> Self {
115 Self { step: 0, config: BackoffConfig::default() }
116 }
117
118 #[inline(always)]
119 pub fn from(config: BackoffConfig) -> Self {
120 Self { step: 0, config }
121 }
122
123 #[allow(dead_code)]
124 #[inline(always)]
125 pub fn spin(&mut self) -> bool {
126 let step = if self.step < SPIN_LIMIT { self.step } else { SPIN_LIMIT };
127 for _ in 0..1 << step {
128 core::hint::spin_loop();
129 }
130 if self.step < MAX_LIMIT {
131 self.step += 1;
132 self.step >= self.config.limit
133 } else {
134 true
135 }
136 }
137
138 #[inline(always)]
139 pub fn set_step(&mut self, step: u16) {
140 self.step = step;
141 }
142
143 #[inline(always)]
144 pub fn snooze(&mut self) -> bool {
145 if self.step < self.config.limit {
146 self.step += 1;
147 if self.step < self.config.spin_limit {
148 for _ in 0..1 << self.step {
149 core::hint::spin_loop();
150 }
151 false
152 } else {
153 #[cfg(feature = "std")]
154 {
155 std::thread::yield_now();
156 false
157 }
158 #[cfg(not(feature = "std"))]
159 {
160 true
161 }
162 }
163 } else {
164 true
165 }
166 }
167
168 #[cfg(feature = "std")]
169 #[inline(always)]
170 pub fn yield_now(&mut self) -> bool {
171 std::thread::yield_now();
172 if self.step < self.config.limit {
173 self.step += 1;
174 self.step >= self.config.limit
175 } else {
176 true
177 }
178 }
179
180 #[inline(always)]
181 pub fn is_completed(&self) -> bool {
182 self.step >= self.config.limit
183 }
184
185 #[inline(always)]
186 pub fn step(&self) -> usize {
187 self.step as usize
188 }
189
190 #[inline(always)]
191 pub fn reset(&mut self) {
192 self.step = 0;
193 }
194}
195
196#[cfg(test)]
197mod tests {
198
199 use super::*;
200
201 #[test]
202 fn test_backoff() {
203 let backoff = Backoff::from(BackoffConfig { spin_limit: 1, limit: 0 });
204 assert!(backoff.is_completed());
205 println!("Option<backoff> size {}", size_of::<Option<Backoff>>());
206 println!("backoff size {}", size_of::<Backoff>());
207 println!("BackoffConfig size {}", size_of::<BackoffConfig>());
208 assert_eq!(size_of::<BackoffConfig>(), size_of::<u32>());
209 let config = BackoffConfig { spin_limit: 6, limit: 7 };
210 let config_i = config.to_u32();
211 let _config = BackoffConfig::from_u32(config_i);
212 assert_eq!(config.spin_limit, _config.spin_limit);
213 assert_eq!(config.limit, _config.limit);
214
215 let mut backoff = Backoff::from(BackoffConfig { spin_limit: 2, limit: 4 });
216 assert_eq!(backoff.step, 0);
217 backoff.spin();
218 assert_eq!(backoff.step, 1);
219 backoff.snooze();
220 assert_eq!(backoff.step, 2);
221 backoff.snooze();
222 backoff.snooze();
223 backoff.snooze();
224 backoff.snooze();
225 assert_eq!(backoff.step, 4);
226 backoff.spin();
227 assert_eq!(backoff.step, 5);
228 }
229}