use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct BackpressureConfig {
pub max_concurrent: usize,
pub high_water_mark: f64,
pub low_water_mark: f64,
pub cooldown: Duration,
pub enabled: bool,
}
impl Default for BackpressureConfig {
fn default() -> Self {
Self {
max_concurrent: 32,
high_water_mark: 0.8,
low_water_mark: 0.5,
cooldown: Duration::from_millis(100),
enabled: true,
}
}
}
impl BackpressureConfig {
pub fn new() -> Self {
Self::default()
}
pub fn high_threshold(&self) -> usize {
(self.max_concurrent as f64 * self.high_water_mark) as usize
}
pub fn low_threshold(&self) -> usize {
(self.max_concurrent as f64 * self.low_water_mark) as usize
}
pub fn validate(&self) -> Result<(), BackpressureError> {
if self.max_concurrent == 0 {
return Err(BackpressureError::InvalidConfig("max_concurrent must be > 0".to_string()));
}
if self.high_water_mark <= self.low_water_mark || self.high_water_mark > 1.0 {
return Err(BackpressureError::InvalidConfig(
"high_water_mark must be > low_water_mark and <= 1.0".to_string(),
));
}
if self.low_water_mark < 0.0 {
return Err(BackpressureError::InvalidConfig("low_water_mark must be >= 0.0".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum BackpressureError {
#[error("Invalid backpressure config: {0}")]
InvalidConfig(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BackpressureState {
#[default]
Normal,
Warning,
Critical,
}
impl BackpressureState {
pub fn as_str(&self) -> &'static str {
match self {
BackpressureState::Normal => "normal",
BackpressureState::Warning => "warning",
BackpressureState::Critical => "critical",
}
}
}
pub struct BackpressureMonitor {
config: BackpressureConfig,
current: AtomicUsize,
total_processed: AtomicU64,
total_rejected: AtomicU64,
state: std::sync::Mutex<BackpressureState>,
last_state_change: std::sync::Mutex<Option<Instant>>,
active: AtomicBool,
}
impl BackpressureMonitor {
pub fn new(config: BackpressureConfig) -> Self {
Self {
config,
current: AtomicUsize::new(0),
total_processed: AtomicU64::new(0),
total_rejected: AtomicU64::new(0),
state: std::sync::Mutex::new(BackpressureState::Normal),
last_state_change: std::sync::Mutex::new(None),
active: AtomicBool::new(false),
}
}
pub fn with_defaults() -> Self {
Self::new(BackpressureConfig::default())
}
pub fn config(&self) -> &BackpressureConfig {
&self.config
}
pub fn current(&self) -> usize {
self.current.load(Ordering::Relaxed)
}
pub fn state(&self) -> BackpressureState {
if let Ok(state) = self.state.lock() {
*state
} else {
BackpressureState::Normal
}
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::Relaxed)
}
pub fn try_acquire(&self) -> bool {
if !self.config.enabled {
self.current.fetch_add(1, Ordering::Relaxed);
self.total_processed.fetch_add(1, Ordering::Relaxed);
return true;
}
let high_threshold = self.config.high_threshold();
loop {
let current = self.current.load(Ordering::Relaxed);
if current >= self.config.max_concurrent {
self.total_rejected.fetch_add(1, Ordering::Relaxed);
return false;
}
let new = current + 1;
match self
.current
.compare_exchange_weak(current, new, Ordering::Relaxed, Ordering::Relaxed)
{
Ok(_) => {
self.total_processed.fetch_add(1, Ordering::Relaxed);
if current >= high_threshold {
self.set_state(BackpressureState::Critical);
self.active.store(true, Ordering::Relaxed);
} else if current >= self.config.low_threshold() {
self.set_state(BackpressureState::Warning);
}
return true;
}
Err(_) => {
continue;
}
}
}
}
pub fn release(&self) {
let prev = match self
.current
.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| current.checked_sub(1))
{
Ok(prev) => prev,
Err(_) => {
tracing::warn!("BackpressureMonitor::release called with no outstanding acquisition; ignoring");
0
}
};
let low_threshold = self.config.low_threshold();
if prev <= low_threshold + 1 {
self.set_state(BackpressureState::Normal);
self.active.store(false, Ordering::Relaxed);
}
}
fn set_state(&self, new_state: BackpressureState) {
if let Ok(mut state) = self.state.lock()
&& *state != new_state
{
*state = new_state;
if let Ok(mut last) = self.last_state_change.lock() {
*last = Some(Instant::now());
}
}
}
pub fn total_processed(&self) -> u64 {
self.total_processed.load(Ordering::Relaxed)
}
pub fn total_rejected(&self) -> u64 {
self.total_rejected.load(Ordering::Relaxed)
}
pub fn rejection_rate(&self) -> f64 {
let processed = self.total_processed.load(Ordering::Relaxed);
let rejected = self.total_rejected.load(Ordering::Relaxed);
let total = processed + rejected;
if total == 0 { 0.0 } else { rejected as f64 / total as f64 }
}
pub fn should_apply_backpressure(&self) -> bool {
if !self.config.enabled {
return false;
}
let current = self.current.load(Ordering::Relaxed);
if current < self.config.high_threshold() {
return false;
}
if let Ok(last) = self.last_state_change.lock()
&& let Some(last_time) = *last
&& last_time.elapsed() < self.config.cooldown
{
return false;
}
true
}
pub fn reset_stats(&self) {
self.total_processed.store(0, Ordering::Relaxed);
self.total_rejected.store(0, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backpressure_config() {
let config = BackpressureConfig::default();
assert!(config.validate().is_ok());
assert_eq!(config.high_threshold(), 25); assert_eq!(config.low_threshold(), 16); }
#[test]
fn test_backpressure_config_validation() {
let config = BackpressureConfig {
max_concurrent: 0,
..Default::default()
};
assert!(config.validate().is_err());
let config = BackpressureConfig {
high_water_mark: 0.3,
low_water_mark: 0.5,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_backpressure_monitor() {
let config = BackpressureConfig {
max_concurrent: 4,
high_water_mark: 0.75, low_water_mark: 0.5, ..Default::default()
};
let monitor = BackpressureMonitor::new(config);
assert!(monitor.try_acquire());
assert!(monitor.try_acquire());
assert!(monitor.try_acquire());
assert!(monitor.try_acquire());
assert!(!monitor.try_acquire());
monitor.release();
monitor.release();
monitor.release();
assert_eq!(monitor.state(), BackpressureState::Normal);
}
#[test]
fn test_release_underflow_stays_at_zero() {
let monitor = BackpressureMonitor::with_defaults();
monitor.release();
assert_eq!(monitor.current(), 0);
assert!(monitor.try_acquire());
assert_eq!(monitor.current(), 1);
monitor.release();
assert_eq!(monitor.current(), 0);
}
#[test]
fn test_rejection_rate() {
let config = BackpressureConfig {
max_concurrent: 2,
..Default::default()
};
let monitor = BackpressureMonitor::new(config);
assert!(monitor.try_acquire());
assert!(monitor.try_acquire());
assert!(!monitor.try_acquire());
assert!((monitor.rejection_rate() - 0.3333333333333333).abs() < 0.01);
}
#[test]
fn test_disabled_backpressure() {
let config = BackpressureConfig {
max_concurrent: 1,
enabled: false,
..Default::default()
};
let monitor = BackpressureMonitor::new(config);
assert!(monitor.try_acquire());
assert!(monitor.try_acquire());
assert!(monitor.try_acquire());
}
}