use std::num::NonZeroUsize;
use std::time::Duration;
use super::tcp::ShutdownHandle;
pub const DEFAULT_MAX_CONNECTIONS: usize = 1024;
const DEFAULT_CONCURRENT_CONNECTIONS: NonZeroUsize =
match NonZeroUsize::new(DEFAULT_MAX_CONNECTIONS) {
Some(value) => value,
None => NonZeroUsize::MIN,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConnectionLimit {
Concurrent(NonZeroUsize),
Lifetime(NonZeroUsize),
Unlimited,
}
impl Default for ConnectionLimit {
fn default() -> Self {
Self::Concurrent(DEFAULT_CONCURRENT_CONNECTIONS)
}
}
pub(crate) struct ConnectionPermit {
release: Option<flume::Sender<()>>,
}
impl ConnectionPermit {
fn noop() -> Self {
Self { release: None }
}
}
impl Drop for ConnectionPermit {
fn drop(&mut self) {
if let Some(release) = self.release.take() {
let _ = release.send(());
}
}
}
#[derive(Clone)]
pub(crate) struct PermitPool {
tx: flume::Sender<()>,
rx: flume::Receiver<()>,
}
impl PermitPool {
fn with_permits(count: NonZeroUsize) -> Self {
let count = count.get();
let (tx, rx) = flume::bounded(count);
for _ in 0..count {
let _ = tx.send(());
}
Self { tx, rx }
}
fn acquire(&self, shutdown: &ShutdownHandle, idle_sleep: Duration) -> Option<ConnectionPermit> {
loop {
if shutdown.is_shutdown() {
return None;
}
match self.rx.recv_timeout(idle_sleep) {
Ok(()) => {
return Some(ConnectionPermit {
release: Some(self.tx.clone()),
});
}
Err(flume::RecvTimeoutError::Timeout) => {}
Err(flume::RecvTimeoutError::Disconnected) => return None,
}
}
}
}
pub(crate) enum Limiter {
Concurrent(PermitPool),
Lifetime(NonZeroUsize),
Unlimited,
}
pub(crate) enum Admission {
Permit(ConnectionPermit),
Shutdown,
}
impl Limiter {
pub(crate) fn from_limit(limit: ConnectionLimit) -> Self {
match limit {
ConnectionLimit::Concurrent(count) => Self::Concurrent(PermitPool::with_permits(count)),
ConnectionLimit::Lifetime(count) => Self::Lifetime(count),
ConnectionLimit::Unlimited => Self::Unlimited,
}
}
pub(crate) fn accepting(&self, accepted: usize) -> bool {
match self {
Self::Lifetime(budget) => accepted < budget.get(),
Self::Concurrent(_) | Self::Unlimited => true,
}
}
pub(crate) fn admit(&self, shutdown: &ShutdownHandle, idle_sleep: Duration) -> Admission {
match self {
Self::Concurrent(pool) => match pool.acquire(shutdown, idle_sleep) {
Some(permit) => Admission::Permit(permit),
None => Admission::Shutdown,
},
Self::Lifetime(_) | Self::Unlimited => Admission::Permit(ConnectionPermit::noop()),
}
}
}
pub(crate) fn stats_lane<T>(limit: ConnectionLimit) -> (flume::Sender<T>, flume::Receiver<T>) {
match limit {
ConnectionLimit::Concurrent(count) | ConnectionLimit::Lifetime(count) => {
flume::bounded(count.get())
}
ConnectionLimit::Unlimited => flume::unbounded(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn nz(value: usize) -> NonZeroUsize {
NonZeroUsize::new(value).expect("nonzero test value")
}
#[test]
fn default_connection_limit_is_concurrent_at_default_magnitude() {
assert_eq!(
ConnectionLimit::default(),
ConnectionLimit::Concurrent(nz(DEFAULT_MAX_CONNECTIONS))
);
assert_eq!(DEFAULT_MAX_CONNECTIONS, 1024);
}
#[test]
fn accepting_caps_only_the_lifetime_budget() {
let lifetime = Limiter::from_limit(ConnectionLimit::Lifetime(nz(2)));
assert!(lifetime.accepting(0));
assert!(lifetime.accepting(1));
assert!(!lifetime.accepting(2));
assert!(!lifetime.accepting(3));
let concurrent = Limiter::from_limit(ConnectionLimit::Concurrent(nz(1)));
assert!(concurrent.accepting(0));
assert!(concurrent.accepting(1_000_000));
let unlimited = Limiter::from_limit(ConnectionLimit::Unlimited);
assert!(unlimited.accepting(0));
assert!(unlimited.accepting(1_000_000));
}
#[test]
fn permit_pool_releases_slot_on_drop_for_reuse() {
let pool = PermitPool::with_permits(nz(1));
let shutdown = ShutdownHandle::new();
let idle = Duration::from_millis(1);
let first = pool
.acquire(&shutdown, idle)
.expect("the single permit is available");
drop(first);
let second = pool.acquire(&shutdown, idle);
assert!(second.is_some(), "a released slot must be reusable");
}
#[test]
fn permit_pool_acquire_returns_none_once_shutdown_when_empty() {
let pool = PermitPool::with_permits(nz(1));
let shutdown = ShutdownHandle::new();
let idle = Duration::from_millis(1);
let _held = pool
.acquire(&shutdown, idle)
.expect("the single permit is available");
shutdown.shutdown();
assert!(pool.acquire(&shutdown, idle).is_none());
}
#[test]
fn admit_hands_a_noop_permit_for_lifetime_and_unlimited() {
let shutdown = ShutdownHandle::new();
let idle = Duration::from_millis(1);
for limiter in [
Limiter::from_limit(ConnectionLimit::Lifetime(nz(1))),
Limiter::from_limit(ConnectionLimit::Unlimited),
] {
assert!(matches!(
limiter.admit(&shutdown, idle),
Admission::Permit(_)
));
}
}
#[test]
fn stats_lane_is_bounded_for_caps_and_unbounded_for_unlimited() {
let (tx, _rx) = stats_lane::<u8>(ConnectionLimit::Concurrent(nz(2)));
assert_eq!(tx.capacity(), Some(2));
let (tx, _rx) = stats_lane::<u8>(ConnectionLimit::Lifetime(nz(3)));
assert_eq!(tx.capacity(), Some(3));
let (tx, _rx) = stats_lane::<u8>(ConnectionLimit::Unlimited);
assert_eq!(tx.capacity(), None);
}
}