use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::Notify;
pub struct AsyncBackpressureController {
threshold: usize,
current: Arc<AtomicUsize>,
notify: Arc<Notify>,
}
impl AsyncBackpressureController {
pub fn new(threshold: usize) -> Self {
Self {
threshold,
current: Arc::new(AtomicUsize::new(0)),
notify: Arc::new(Notify::new()),
}
}
pub async fn allow_push(&self) -> bool {
if self.threshold == 0 {
self.notify.notified().await;
return false;
}
loop {
if self.current.load(Ordering::Relaxed) < self.threshold {
return true;
}
self.notify.notified().await;
}
}
pub fn try_allow_push(&self) -> bool {
if self.threshold == 0 {
return false;
}
self.current.load(Ordering::Relaxed) < self.threshold
}
pub fn push(&self) {
self.current.fetch_add(1, Ordering::Relaxed);
}
pub fn pop(&self) {
self.current.fetch_sub(1, Ordering::Relaxed);
self.notify.notify_one();
}
pub fn pending(&self) -> usize {
self.current.load(Ordering::Relaxed)
}
pub fn threshold(&self) -> usize {
self.threshold
}
}
impl Clone for AsyncBackpressureController {
fn clone(&self) -> Self {
Self {
threshold: self.threshold,
current: Arc::clone(&self.current),
notify: Arc::clone(&self.notify),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pending_below_threshold() {
let controller = AsyncBackpressureController::new(100);
assert!(controller.try_allow_push());
assert_eq!(controller.pending(), 0);
}
#[test]
fn push_increments_pending() {
let controller = AsyncBackpressureController::new(100);
controller.push();
controller.push();
assert_eq!(controller.pending(), 2);
}
#[test]
fn pop_decrements_pending() {
let controller = AsyncBackpressureController::new(100);
controller.push();
controller.push();
controller.pop();
assert_eq!(controller.pending(), 1);
}
#[test]
fn try_allow_push_at_threshold() {
let controller = AsyncBackpressureController::new(2);
controller.push();
controller.push();
assert!(!controller.try_allow_push());
}
#[test]
fn try_allow_push_after_pop() {
let controller = AsyncBackpressureController::new(2);
controller.push();
controller.push();
assert!(!controller.try_allow_push());
controller.pop();
assert!(controller.try_allow_push());
}
#[test]
fn threshold_zero_always_block() {
let controller = AsyncBackpressureController::new(0);
assert!(!controller.try_allow_push());
}
#[tokio::test]
async fn allow_push_below_threshold() {
let controller = AsyncBackpressureController::new(10);
controller.push();
assert!(controller.allow_push().await);
}
#[tokio::test]
async fn allow_push_with_pop_wakeup() {
let controller = AsyncBackpressureController::new(1);
controller.push();
let controller_clone = controller.clone();
let handle = tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
controller_clone.pop();
});
assert!(controller.allow_push().await);
handle.await.unwrap();
}
#[test]
fn clone_shares_state() {
let controller = AsyncBackpressureController::new(100);
let clone = controller.clone();
controller.push();
assert_eq!(clone.pending(), 1);
clone.pop();
assert_eq!(controller.pending(), 0);
}
}