rustfs_io_core/
backpressure.rs1use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
21use std::time::{Duration, Instant};
22
23#[derive(Debug, Clone)]
25pub struct BackpressureConfig {
26 pub max_concurrent: usize,
28 pub high_water_mark: f64,
30 pub low_water_mark: f64,
32 pub cooldown: Duration,
34 pub enabled: bool,
36}
37
38impl Default for BackpressureConfig {
39 fn default() -> Self {
40 Self {
41 max_concurrent: 32,
42 high_water_mark: 0.8,
43 low_water_mark: 0.5,
44 cooldown: Duration::from_millis(100),
45 enabled: true,
46 }
47 }
48}
49
50impl BackpressureConfig {
51 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn high_threshold(&self) -> usize {
58 (self.max_concurrent as f64 * self.high_water_mark) as usize
59 }
60
61 pub fn low_threshold(&self) -> usize {
63 (self.max_concurrent as f64 * self.low_water_mark) as usize
64 }
65
66 pub fn validate(&self) -> Result<(), BackpressureError> {
68 if self.max_concurrent == 0 {
69 return Err(BackpressureError::InvalidConfig("max_concurrent must be > 0".to_string()));
70 }
71 if self.high_water_mark <= self.low_water_mark || self.high_water_mark > 1.0 {
72 return Err(BackpressureError::InvalidConfig(
73 "high_water_mark must be > low_water_mark and <= 1.0".to_string(),
74 ));
75 }
76 if self.low_water_mark < 0.0 {
77 return Err(BackpressureError::InvalidConfig("low_water_mark must be >= 0.0".to_string()));
78 }
79 Ok(())
80 }
81}
82
83#[derive(Debug, Clone, thiserror::Error)]
85pub enum BackpressureError {
86 #[error("Invalid backpressure config: {0}")]
88 InvalidConfig(String),
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
93pub enum BackpressureState {
94 #[default]
96 Normal,
97 Warning,
99 Critical,
101}
102
103impl BackpressureState {
104 pub fn as_str(&self) -> &'static str {
106 match self {
107 BackpressureState::Normal => "normal",
108 BackpressureState::Warning => "warning",
109 BackpressureState::Critical => "critical",
110 }
111 }
112}
113
114pub struct BackpressureMonitor {
116 config: BackpressureConfig,
118 current: AtomicUsize,
120 total_processed: AtomicU64,
122 total_rejected: AtomicU64,
124 state: std::sync::Mutex<BackpressureState>,
126 last_state_change: std::sync::Mutex<Option<Instant>>,
128 active: AtomicBool,
130}
131
132impl BackpressureMonitor {
133 pub fn new(config: BackpressureConfig) -> Self {
135 Self {
136 config,
137 current: AtomicUsize::new(0),
138 total_processed: AtomicU64::new(0),
139 total_rejected: AtomicU64::new(0),
140 state: std::sync::Mutex::new(BackpressureState::Normal),
141 last_state_change: std::sync::Mutex::new(None),
142 active: AtomicBool::new(false),
143 }
144 }
145
146 pub fn with_defaults() -> Self {
148 Self::new(BackpressureConfig::default())
149 }
150
151 pub fn config(&self) -> &BackpressureConfig {
153 &self.config
154 }
155
156 pub fn current(&self) -> usize {
158 self.current.load(Ordering::Relaxed)
159 }
160
161 pub fn state(&self) -> BackpressureState {
163 if let Ok(state) = self.state.lock() {
164 *state
165 } else {
166 BackpressureState::Normal
167 }
168 }
169
170 pub fn is_active(&self) -> bool {
172 self.active.load(Ordering::Relaxed)
173 }
174
175 pub fn try_acquire(&self) -> bool {
179 if !self.config.enabled {
180 self.current.fetch_add(1, Ordering::Relaxed);
181 self.total_processed.fetch_add(1, Ordering::Relaxed);
182 return true;
183 }
184
185 let high_threshold = self.config.high_threshold();
186
187 loop {
189 let current = self.current.load(Ordering::Relaxed);
190
191 if current >= self.config.max_concurrent {
192 self.total_rejected.fetch_add(1, Ordering::Relaxed);
194 return false;
195 }
196
197 let new = current + 1;
198 match self
199 .current
200 .compare_exchange_weak(current, new, Ordering::Relaxed, Ordering::Relaxed)
201 {
202 Ok(_) => {
203 self.total_processed.fetch_add(1, Ordering::Relaxed);
205
206 if current >= high_threshold {
208 self.set_state(BackpressureState::Critical);
209 self.active.store(true, Ordering::Relaxed);
210 } else if current >= self.config.low_threshold() {
211 self.set_state(BackpressureState::Warning);
212 }
213
214 return true;
215 }
216 Err(_) => {
217 continue;
219 }
220 }
221 }
222 }
223
224 pub fn release(&self) {
226 let prev = match self
229 .current
230 .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| current.checked_sub(1))
231 {
232 Ok(prev) => prev,
233 Err(_) => {
234 tracing::warn!("BackpressureMonitor::release called with no outstanding acquisition; ignoring");
235 0
236 }
237 };
238 let low_threshold = self.config.low_threshold();
239
240 if prev <= low_threshold + 1 {
242 self.set_state(BackpressureState::Normal);
243 self.active.store(false, Ordering::Relaxed);
244 }
245 }
246
247 fn set_state(&self, new_state: BackpressureState) {
249 if let Ok(mut state) = self.state.lock()
250 && *state != new_state
251 {
252 *state = new_state;
253 if let Ok(mut last) = self.last_state_change.lock() {
254 *last = Some(Instant::now());
255 }
256 }
257 }
258
259 pub fn total_processed(&self) -> u64 {
261 self.total_processed.load(Ordering::Relaxed)
262 }
263
264 pub fn total_rejected(&self) -> u64 {
266 self.total_rejected.load(Ordering::Relaxed)
267 }
268
269 pub fn rejection_rate(&self) -> f64 {
271 let processed = self.total_processed.load(Ordering::Relaxed);
272 let rejected = self.total_rejected.load(Ordering::Relaxed);
273 let total = processed + rejected;
274 if total == 0 { 0.0 } else { rejected as f64 / total as f64 }
275 }
276
277 pub fn should_apply_backpressure(&self) -> bool {
279 if !self.config.enabled {
280 return false;
281 }
282
283 let current = self.current.load(Ordering::Relaxed);
284 if current < self.config.high_threshold() {
285 return false;
286 }
287
288 if let Ok(last) = self.last_state_change.lock()
290 && let Some(last_time) = *last
291 && last_time.elapsed() < self.config.cooldown
292 {
293 return false;
294 }
295
296 true
297 }
298
299 pub fn reset_stats(&self) {
301 self.total_processed.store(0, Ordering::Relaxed);
302 self.total_rejected.store(0, Ordering::Relaxed);
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn test_backpressure_config() {
312 let config = BackpressureConfig::default();
313 assert!(config.validate().is_ok());
314 assert_eq!(config.high_threshold(), 25); assert_eq!(config.low_threshold(), 16); }
317
318 #[test]
319 fn test_backpressure_config_validation() {
320 let config = BackpressureConfig {
321 max_concurrent: 0,
322 ..Default::default()
323 };
324 assert!(config.validate().is_err());
325
326 let config = BackpressureConfig {
327 high_water_mark: 0.3,
328 low_water_mark: 0.5,
329 ..Default::default()
330 };
331 assert!(config.validate().is_err());
332 }
333
334 #[test]
335 fn test_backpressure_monitor() {
336 let config = BackpressureConfig {
337 max_concurrent: 4,
338 high_water_mark: 0.75, low_water_mark: 0.5, ..Default::default()
341 };
342 let monitor = BackpressureMonitor::new(config);
343
344 assert!(monitor.try_acquire());
346 assert!(monitor.try_acquire());
350 assert!(monitor.try_acquire());
354 assert!(monitor.try_acquire());
358 assert!(!monitor.try_acquire()); monitor.release();
364 monitor.release();
368 monitor.release();
372 assert_eq!(monitor.state(), BackpressureState::Normal);
374 }
375
376 #[test]
377 fn test_release_underflow_stays_at_zero() {
378 let monitor = BackpressureMonitor::with_defaults();
381
382 monitor.release();
383 assert_eq!(monitor.current(), 0);
384
385 assert!(monitor.try_acquire());
387 assert_eq!(monitor.current(), 1);
388 monitor.release();
389 assert_eq!(monitor.current(), 0);
390 }
391
392 #[test]
393 fn test_rejection_rate() {
394 let config = BackpressureConfig {
395 max_concurrent: 2,
396 ..Default::default()
397 };
398 let monitor = BackpressureMonitor::new(config);
399
400 assert!(monitor.try_acquire());
401 assert!(monitor.try_acquire());
402 assert!(!monitor.try_acquire()); assert!((monitor.rejection_rate() - 0.3333333333333333).abs() < 0.01);
405 }
406
407 #[test]
408 fn test_disabled_backpressure() {
409 let config = BackpressureConfig {
410 max_concurrent: 1,
411 enabled: false,
412 ..Default::default()
413 };
414 let monitor = BackpressureMonitor::new(config);
415
416 assert!(monitor.try_acquire());
418 assert!(monitor.try_acquire());
419 assert!(monitor.try_acquire());
420 }
421}