use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IngressLimits {
pub max_body_bytes: usize,
pub inflight_ceiling: usize,
pub max_connections: usize,
}
impl Default for IngressLimits {
fn default() -> Self {
Self {
max_body_bytes: 8 * 1024 * 1024,
inflight_ceiling: 256 * 1024 * 1024,
max_connections: 16 * 1024,
}
}
}
#[derive(Debug)]
pub(crate) struct Admission {
inflight: AtomicUsize,
ceiling: usize,
}
impl Admission {
pub(crate) fn new(ceiling: usize) -> Self {
Self {
inflight: AtomicUsize::new(0),
ceiling,
}
}
pub(crate) fn try_reserve(self: &Arc<Self>, amount: usize) -> Option<Reservation> {
let mut current = self.inflight.load(Ordering::Acquire);
loop {
let next = current.checked_add(amount)?;
if next > self.ceiling {
return None;
}
match self.inflight.compare_exchange_weak(
current,
next,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
return Some(Reservation {
admission: Arc::clone(self),
amount,
})
}
Err(actual) => current = actual,
}
}
}
}
#[derive(Debug)]
pub(crate) struct Reservation {
admission: Arc<Admission>,
amount: usize,
}
impl Drop for Reservation {
fn drop(&mut self) {
self.admission
.inflight
.fetch_sub(self.amount, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reserves_up_to_the_ceiling_then_sheds() {
let admission = Arc::new(Admission::new(100));
let a = admission.try_reserve(60).expect("first fits");
let b = admission.try_reserve(40).expect("second fills it exactly");
assert!(admission.try_reserve(1).is_none(), "over ceiling must shed");
drop(a);
assert!(
admission.try_reserve(50).is_some(),
"freed budget is reusable"
);
drop(b);
}
#[test]
fn an_amount_over_the_whole_ceiling_is_shed() {
let admission = Arc::new(Admission::new(10));
assert!(admission.try_reserve(11).is_none());
}
}