use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use uuid::Uuid;
use crate::error::ApiError;
pub const DEFAULT_SSE_MAX_PER_PRINCIPAL: usize = 5;
pub const DEFAULT_SSE_MAX_GLOBAL: usize = 1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SseLimits {
pub per_principal: Option<usize>,
pub global: Option<usize>,
}
impl Default for SseLimits {
fn default() -> Self {
Self {
per_principal: Some(DEFAULT_SSE_MAX_PER_PRINCIPAL),
global: Some(DEFAULT_SSE_MAX_GLOBAL),
}
}
}
#[derive(Debug, Default)]
struct Counts {
total: usize,
per_principal: HashMap<Uuid, usize>,
}
#[derive(Debug, Clone, Default)]
pub struct SseLimiter {
limits: SseLimits,
counts: Arc<Mutex<Counts>>,
}
impl SseLimiter {
pub fn new(limits: SseLimits) -> Self {
Self {
limits,
counts: Arc::new(Mutex::new(Counts::default())),
}
}
pub fn try_acquire(&self, principal: Uuid) -> Result<SseConnection, ApiError> {
let mut counts = self.lock();
if self.limits.global.is_some_and(|max| counts.total >= max) {
return Err(ApiError::SseConnectionLimit);
}
let held = counts.per_principal.get(&principal).copied().unwrap_or(0);
if self.limits.per_principal.is_some_and(|max| held >= max) {
return Err(ApiError::SseConnectionLimit);
}
counts.total += 1;
counts.per_principal.insert(principal, held + 1);
Ok(SseConnection {
limiter: self.clone(),
principal,
})
}
pub fn open_connections(&self) -> usize {
self.lock().total
}
fn lock(&self) -> MutexGuard<'_, Counts> {
self.counts.lock().unwrap_or_else(PoisonError::into_inner)
}
fn release(&self, principal: Uuid) {
let mut counts = self.lock();
counts.total = counts.total.saturating_sub(1);
if let Some(held) = counts.per_principal.get_mut(&principal) {
*held = held.saturating_sub(1);
if *held == 0 {
counts.per_principal.remove(&principal);
}
}
}
}
#[derive(Debug)]
pub struct SseConnection {
limiter: SseLimiter,
principal: Uuid,
}
impl Drop for SseConnection {
fn drop(&mut self) {
self.limiter.release(self.principal);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn limiter(per_principal: Option<usize>, global: Option<usize>) -> SseLimiter {
SseLimiter::new(SseLimits {
per_principal,
global,
})
}
#[test]
fn per_principal_cap_refuses_the_next_stream() {
let limiter = SseLimiter::default();
let user = Uuid::now_v7();
let held: Vec<_> = (0..DEFAULT_SSE_MAX_PER_PRINCIPAL)
.map(|_| limiter.try_acquire(user).expect("under the cap"))
.collect();
let refused = limiter.try_acquire(user);
assert!(matches!(refused, Err(ApiError::SseConnectionLimit)));
let other = limiter.try_acquire(Uuid::now_v7());
assert!(other.is_ok(), "another principal is unaffected");
assert_eq!(held.len(), DEFAULT_SSE_MAX_PER_PRINCIPAL);
}
#[test]
fn global_cap_refuses_every_principal() {
let limiter = limiter(None, Some(2));
let _a = limiter.try_acquire(Uuid::now_v7()).expect("first");
let _b = limiter.try_acquire(Uuid::now_v7()).expect("second");
let refused = limiter.try_acquire(Uuid::now_v7());
assert!(matches!(refused, Err(ApiError::SseConnectionLimit)));
}
#[test]
fn dropping_a_connection_frees_its_slot() {
let limiter = limiter(Some(1), Some(1));
let user = Uuid::now_v7();
let connection = limiter.try_acquire(user).expect("first");
assert!(limiter.try_acquire(user).is_err());
drop(connection);
assert!(limiter.try_acquire(user).is_ok());
}
#[test]
fn unlimited_when_limits_are_none() {
let limiter = limiter(None, None);
let user = Uuid::now_v7();
let held: Vec<_> = (0..50)
.map(|_| limiter.try_acquire(user).expect("unlimited"))
.collect();
assert_eq!(limiter.open_connections(), held.len());
}
#[test]
fn zero_limit_refuses_everything() {
let limiter = limiter(Some(0), None);
assert!(limiter.try_acquire(Uuid::now_v7()).is_err());
}
#[test]
fn counters_return_to_zero() {
let limiter = SseLimiter::default();
let user = Uuid::now_v7();
let a = limiter.try_acquire(user).expect("a");
let b = limiter.try_acquire(user).expect("b");
let c = limiter.try_acquire(Uuid::now_v7()).expect("c");
assert_eq!(limiter.open_connections(), 3);
drop((a, b, c));
assert_eq!(limiter.open_connections(), 0);
assert!(limiter.lock().per_principal.is_empty());
}
#[test]
fn clones_share_counters() {
let limiter = limiter(Some(1), None);
let clone = limiter.clone();
let user = Uuid::now_v7();
let _connection = limiter.try_acquire(user).expect("first");
assert!(clone.try_acquire(user).is_err());
}
#[test]
fn connection_is_send_and_static() {
fn assert_send_static<T: Send + 'static>() {}
assert_send_static::<SseConnection>();
}
}