use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::Duration;
use tokio::sync::Semaphore;
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub max_connections: usize,
pub max_concurrent_requests: usize,
pub idle_timeout: Duration,
pub request_timeout: Duration,
pub drain_timeout: Duration,
}
impl Default for ConnectionConfig {
fn default() -> Self {
Self {
max_connections: 1024,
max_concurrent_requests: 256,
idle_timeout: Duration::from_secs(300), request_timeout: Duration::from_secs(30), drain_timeout: Duration::from_secs(10), }
}
}
pub struct ConnectionManager {
config: ConnectionConfig,
connection_semaphore: Arc<Semaphore>,
request_semaphore: Arc<Semaphore>,
shutting_down: Arc<AtomicBool>,
active_connections: Arc<AtomicU64>,
rejected_connections: AtomicU64,
}
impl ConnectionManager {
pub fn new(config: ConnectionConfig) -> Self {
Self {
connection_semaphore: Arc::new(Semaphore::new(config.max_connections)),
request_semaphore: Arc::new(Semaphore::new(config.max_concurrent_requests)),
shutting_down: Arc::new(AtomicBool::new(false)),
active_connections: Arc::new(AtomicU64::new(0)),
rejected_connections: AtomicU64::new(0),
config,
}
}
pub fn try_accept(&self) -> Option<ConnectionGuard> {
if self.shutting_down.load(Ordering::Relaxed) {
return None;
}
match self.connection_semaphore.clone().try_acquire_owned() {
Ok(permit) => {
self.active_connections.fetch_add(1, Ordering::Relaxed);
Some(ConnectionGuard {
_permit: permit,
active_connections: Arc::clone(&self.active_connections),
})
}
Err(_) => {
self.rejected_connections.fetch_add(1, Ordering::Relaxed);
None
}
}
}
pub async fn acquire_request_permit(&self) -> Option<tokio::sync::OwnedSemaphorePermit> {
tokio::time::timeout(
self.config.request_timeout,
self.request_semaphore.clone().acquire_owned(),
)
.await
.ok()
.and_then(|r| r.ok())
}
pub fn idle_timeout(&self) -> Duration {
self.config.idle_timeout
}
pub fn request_timeout(&self) -> Duration {
self.config.request_timeout
}
pub fn is_shutting_down(&self) -> bool {
self.shutting_down.load(Ordering::Relaxed)
}
pub fn initiate_shutdown(&self) {
self.shutting_down.store(true, Ordering::Relaxed);
}
pub fn active_connections(&self) -> u64 {
self.active_connections.load(Ordering::Relaxed)
}
pub fn rejected_connections(&self) -> u64 {
self.rejected_connections.load(Ordering::Relaxed)
}
pub fn drain_timeout(&self) -> Duration {
self.config.drain_timeout
}
pub async fn wait_for_drain(&self) {
let start = tokio::time::Instant::now();
while self.active_connections() > 0 {
if start.elapsed() >= self.config.drain_timeout {
tracing::warn!(
"Drain timeout expired with {} active connections",
self.active_connections()
);
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
}
}
pub struct ConnectionGuard {
_permit: tokio::sync::OwnedSemaphorePermit,
active_connections: Arc<AtomicU64>,
}
impl Drop for ConnectionGuard {
fn drop(&mut self) {
self.active_connections.fetch_sub(1, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config() {
let config = ConnectionConfig::default();
assert_eq!(config.max_connections, 1024);
assert_eq!(config.max_concurrent_requests, 256);
assert_eq!(config.idle_timeout, Duration::from_secs(300));
}
#[test]
fn connection_limit_enforced() {
let config = ConnectionConfig {
max_connections: 2,
max_concurrent_requests: 10,
..Default::default()
};
let mgr = ConnectionManager::new(config);
let _g1 = mgr.try_accept().expect("first connection should succeed");
let _g2 = mgr.try_accept().expect("second connection should succeed");
assert!(
mgr.try_accept().is_none(),
"third connection should be rejected"
);
assert_eq!(mgr.active_connections(), 2);
assert_eq!(mgr.rejected_connections(), 1);
}
#[test]
fn connection_released_on_drop() {
let config = ConnectionConfig {
max_connections: 1,
..Default::default()
};
let mgr = ConnectionManager::new(config);
{
let _g = mgr.try_accept().expect("should succeed");
assert_eq!(mgr.active_connections(), 1);
}
assert_eq!(mgr.active_connections(), 0);
let _g = mgr.try_accept().expect("should succeed after release");
}
#[test]
fn shutdown_rejects_new_connections() {
let mgr = ConnectionManager::new(ConnectionConfig::default());
assert!(!mgr.is_shutting_down());
mgr.initiate_shutdown();
assert!(mgr.is_shutting_down());
assert!(mgr.try_accept().is_none());
}
#[tokio::test]
async fn request_permit_works() {
let config = ConnectionConfig {
max_concurrent_requests: 2,
..Default::default()
};
let mgr = ConnectionManager::new(config);
let _p1 = mgr
.acquire_request_permit()
.await
.expect("should get permit");
let _p2 = mgr
.acquire_request_permit()
.await
.expect("should get permit");
}
#[tokio::test]
async fn drain_completes_when_no_connections() {
let mgr = ConnectionManager::new(ConnectionConfig {
drain_timeout: Duration::from_millis(100),
..Default::default()
});
mgr.wait_for_drain().await;
}
}