use std::sync::{
Arc,
atomic::{AtomicU64, AtomicUsize, Ordering},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Pool {
Handshake,
Application,
Control,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Limits {
pub handshakes: usize,
pub application: usize,
pub control: usize,
}
impl Default for Limits {
fn default() -> Self {
Self {
handshakes: 32,
application: 256,
control: 32,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PoolSnapshot {
pub limit: usize,
pub active: usize,
pub rejected: u64,
}
struct Capacity {
limit: usize,
active: AtomicUsize,
rejected: AtomicU64,
}
pub struct Admission {
pools: [Capacity; 3],
}
impl Admission {
pub fn new(limits: Limits) -> Self {
Self {
pools: [limits.handshakes, limits.application, limits.control].map(|limit| Capacity {
limit,
active: AtomicUsize::new(0),
rejected: AtomicU64::new(0),
}),
}
}
pub fn try_acquire(self: &Arc<Self>, pool: Pool) -> Option<Permit> {
let capacity = &self.pools[pool as usize];
if capacity
.active
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |active| {
(active < capacity.limit).then(|| active + 1)
})
.is_err()
{
capacity.rejected.fetch_add(1, Ordering::Relaxed);
return None;
}
Some(Permit {
admission: Arc::clone(self),
pool,
})
}
pub fn snapshot(&self, pool: Pool) -> PoolSnapshot {
let capacity = &self.pools[pool as usize];
PoolSnapshot {
limit: capacity.limit,
active: capacity.active.load(Ordering::Relaxed),
rejected: capacity.rejected.load(Ordering::Relaxed),
}
}
}
#[must_use = "hold the permit until the session ends"]
pub struct Permit {
admission: Arc<Admission>,
pool: Pool,
}
impl Drop for Permit {
fn drop(&mut self) {
self.admission.pools[self.pool as usize]
.active
.fetch_sub(1, Ordering::Relaxed);
}
}