use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Copy)]
pub struct PoolConfig {
pub max_connections: usize,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
max_connections: 10_000,
}
}
}
impl PoolConfig {
pub fn new(max_connections: usize) -> Self {
Self { max_connections }
}
pub fn validate(&self) -> Result<(), String> {
if self.max_connections == 0 {
return Err("max_connections must be > 0".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PooledConnection {
pub connection_id: String,
pub user_id: Option<i64>,
pub last_active_at: i64,
pub created_at: i64,
pub messages_sent: u64,
pub messages_received: u64,
}
impl PooledConnection {
pub fn new(connection_id: impl Into<String>, now_ms: i64) -> Self {
Self {
connection_id: connection_id.into(),
user_id: None,
last_active_at: now_ms,
created_at: now_ms,
messages_sent: 0,
messages_received: 0,
}
}
pub fn with_user(mut self, user_id: i64) -> Self {
self.user_id = Some(user_id);
self
}
pub fn touch(&mut self, now_ms: i64) {
self.last_active_at = now_ms;
}
pub fn record_sent(&mut self) {
self.messages_sent += 1;
}
pub fn record_received(&mut self) {
self.messages_received += 1;
}
pub fn idle_ms(&self, now_ms: i64) -> i64 {
now_ms - self.last_active_at
}
pub fn uptime_ms(&self, now_ms: i64) -> i64 {
now_ms - self.created_at
}
}
#[derive(Debug)]
pub struct ConnectionPool {
config: PoolConfig,
connections: Arc<RwLock<HashMap<String, PooledConnection>>>,
lru_order: Arc<RwLock<VecDeque<String>>>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum AdmitResult {
Admitted,
AlreadyExists,
EvictedAndAdmitted { evicted_id: String },
}
impl ConnectionPool {
pub fn new(config: PoolConfig) -> Self {
Self {
config,
connections: Arc::new(RwLock::new(HashMap::new())),
lru_order: Arc::new(RwLock::new(VecDeque::new())),
}
}
pub fn config(&self) -> &PoolConfig {
&self.config
}
pub async fn admit(&self, connection_id: impl Into<String>, now_ms: i64) -> AdmitResult {
let id = connection_id.into();
let mut connections = self.connections.write().await;
if connections.contains_key(&id) {
return AdmitResult::AlreadyExists;
}
let mut lru = self.lru_order.write().await;
let evicted = if connections.len() >= self.config.max_connections {
let mut evicted_id = None;
while let Some(candidate) = lru.pop_back() {
if connections.contains_key(&candidate) {
connections.remove(&candidate);
evicted_id = Some(candidate);
break;
}
}
evicted_id
} else {
None
};
connections.insert(id.clone(), PooledConnection::new(&id, now_ms));
lru.push_front(id.clone());
match evicted {
Some(evicted_id) => AdmitResult::EvictedAndAdmitted { evicted_id },
None => AdmitResult::Admitted,
}
}
pub async fn remove(&self, connection_id: &str) -> Option<PooledConnection> {
let mut connections = self.connections.write().await;
let removed = connections.remove(connection_id);
if removed.is_some() {
let mut lru = self.lru_order.write().await;
lru.retain(|id| id != connection_id);
}
removed
}
pub async fn touch(&self, connection_id: &str, now_ms: i64) -> bool {
let mut connections = self.connections.write().await;
if let Some(conn) = connections.get_mut(connection_id) {
conn.touch(now_ms);
drop(connections);
let mut lru = self.lru_order.write().await;
lru.retain(|id| id != connection_id);
lru.push_front(connection_id.to_string());
return true;
}
false
}
pub async fn record_sent(&self, connection_id: &str) -> bool {
let mut connections = self.connections.write().await;
if let Some(conn) = connections.get_mut(connection_id) {
conn.record_sent();
return true;
}
false
}
pub async fn record_received(&self, connection_id: &str) -> bool {
let mut connections = self.connections.write().await;
if let Some(conn) = connections.get_mut(connection_id) {
conn.record_received();
return true;
}
false
}
pub async fn get(&self, connection_id: &str) -> Option<PooledConnection> {
let connections = self.connections.read().await;
connections.get(connection_id).cloned()
}
pub async fn count(&self) -> usize {
let connections = self.connections.read().await;
connections.len()
}
pub async fn is_full(&self) -> bool {
self.count().await >= self.config.max_connections
}
pub async fn find_by_user(&self, user_id: i64) -> Vec<PooledConnection> {
let connections = self.connections.read().await;
let mut result: Vec<PooledConnection> = connections
.values()
.filter(|c| c.user_id == Some(user_id))
.cloned()
.collect();
result.sort_by(|a, b| a.connection_id.cmp(&b.connection_id));
result
}
pub async fn evict_idle(&self, idle_threshold_ms: i64, now_ms: i64) -> usize {
let mut connections = self.connections.write().await;
let mut lru = self.lru_order.write().await;
let before = connections.len();
let to_remove: Vec<String> = connections
.iter()
.filter(|(_, c)| c.idle_ms(now_ms) >= idle_threshold_ms)
.map(|(id, _)| id.clone())
.collect();
for id in &to_remove {
connections.remove(id);
}
lru.retain(|id| !to_remove.contains(id));
before - connections.len()
}
pub async fn clear(&self) {
let mut connections = self.connections.write().await;
let mut lru = self.lru_order.write().await;
connections.clear();
lru.clear();
}
pub async fn lru_order_list(&self) -> Vec<String> {
let lru = self.lru_order.read().await;
lru.iter().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_config_default() {
let cfg = PoolConfig::default();
assert_eq!(cfg.max_connections, 10_000);
}
#[test]
fn test_pool_config_validate_ok() {
let cfg = PoolConfig::new(100);
assert!(cfg.validate().is_ok());
}
#[test]
fn test_pool_config_validate_zero() {
let cfg = PoolConfig::new(0);
assert!(cfg.validate().is_err());
}
#[test]
fn test_pooled_connection_new() {
let conn = PooledConnection::new("c1", 1000);
assert_eq!(conn.connection_id, "c1");
assert!(conn.user_id.is_none());
assert_eq!(conn.last_active_at, 1000);
assert_eq!(conn.created_at, 1000);
assert_eq!(conn.messages_sent, 0);
assert_eq!(conn.messages_received, 0);
}
#[test]
fn test_pooled_connection_with_user() {
let conn = PooledConnection::new("c1", 1000).with_user(42);
assert_eq!(conn.user_id, Some(42));
}
#[test]
fn test_pooled_connection_touch_updates_last_active() {
let mut conn = PooledConnection::new("c1", 1000);
conn.touch(2000);
assert_eq!(conn.last_active_at, 2000);
}
#[test]
fn test_pooled_connection_record_sent_and_received() {
let mut conn = PooledConnection::new("c1", 1000);
conn.record_sent();
conn.record_sent();
conn.record_received();
assert_eq!(conn.messages_sent, 2);
assert_eq!(conn.messages_received, 1);
}
#[test]
fn test_pooled_connection_idle_ms() {
let conn = PooledConnection::new("c1", 1000);
assert_eq!(conn.idle_ms(1500), 500);
}
#[test]
fn test_pooled_connection_uptime_ms() {
let conn = PooledConnection::new("c1", 1000);
assert_eq!(conn.uptime_ms(3000), 2000);
}
#[tokio::test]
async fn test_pool_admit_new_connection() {
let pool = ConnectionPool::new(PoolConfig::new(10));
let result = pool.admit("c1", 1000).await;
assert_eq!(result, AdmitResult::Admitted);
assert_eq!(pool.count().await, 1);
}
#[tokio::test]
async fn test_pool_admit_duplicate_returns_already_exists() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
let result = pool.admit("c1", 2000).await;
assert_eq!(result, AdmitResult::AlreadyExists);
assert_eq!(pool.count().await, 1);
}
#[tokio::test]
async fn test_pool_admit_evicts_lru_when_full() {
let pool = ConnectionPool::new(PoolConfig::new(2));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await;
let result = pool.admit("c3", 3000).await;
match result {
AdmitResult::EvictedAndAdmitted { evicted_id } => {
assert_eq!(evicted_id, "c1");
}
_ => panic!("expected EvictedAndAdmitted, got {:?}", result),
}
assert_eq!(pool.count().await, 2);
assert!(pool.get("c1").await.is_none());
assert!(pool.get("c2").await.is_some());
assert!(pool.get("c3").await.is_some());
}
#[tokio::test]
async fn test_pool_admit_touch_updates_lru_order() {
let pool = ConnectionPool::new(PoolConfig::new(2));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await;
pool.touch("c1", 5000).await;
let result = pool.admit("c3", 6000).await;
match result {
AdmitResult::EvictedAndAdmitted { evicted_id } => {
assert_eq!(evicted_id, "c2");
}
_ => panic!("expected c2 to be evicted"),
}
}
#[tokio::test]
async fn test_pool_remove() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
let removed = pool.remove("c1").await;
assert!(removed.is_some());
assert_eq!(pool.count().await, 0);
}
#[tokio::test]
async fn test_pool_remove_missing_returns_none() {
let pool = ConnectionPool::new(PoolConfig::new(10));
assert!(pool.remove("ghost").await.is_none());
}
#[tokio::test]
async fn test_pool_touch_updates_last_active() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
pool.touch("c1", 5000).await;
let conn = pool.get("c1").await.unwrap();
assert_eq!(conn.last_active_at, 5000);
}
#[tokio::test]
async fn test_pool_touch_unknown_returns_false() {
let pool = ConnectionPool::new(PoolConfig::new(10));
assert!(!pool.touch("ghost", 1000).await);
}
#[tokio::test]
async fn test_pool_record_sent_and_received() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
assert!(pool.record_sent("c1").await);
assert!(pool.record_received("c1").await);
let conn = pool.get("c1").await.unwrap();
assert_eq!(conn.messages_sent, 1);
assert_eq!(conn.messages_received, 1);
}
#[tokio::test]
async fn test_pool_record_sent_unknown_returns_false() {
let pool = ConnectionPool::new(PoolConfig::new(10));
assert!(!pool.record_sent("ghost").await);
}
#[tokio::test]
async fn test_pool_find_by_user() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
pool.admit("c2", 1000).await;
{
let mut conns = pool.connections.write().await;
conns.get_mut("c1").unwrap().user_id = Some(100);
conns.get_mut("c2").unwrap().user_id = Some(200);
}
let found = pool.find_by_user(100).await;
assert_eq!(found.len(), 1);
assert_eq!(found[0].connection_id, "c1");
}
#[tokio::test]
async fn test_pool_find_by_user_none() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
let found = pool.find_by_user(999).await;
assert!(found.is_empty());
}
#[tokio::test]
async fn test_pool_evict_idle() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await;
pool.admit("c3", 5000).await;
let evicted = pool.evict_idle(3000, 6000).await;
assert_eq!(evicted, 2); assert_eq!(pool.count().await, 1);
assert!(pool.get("c3").await.is_some());
}
#[tokio::test]
async fn test_pool_evict_idle_none() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
let evicted = pool.evict_idle(100_000, 2000).await;
assert_eq!(evicted, 0);
}
#[tokio::test]
async fn test_pool_clear() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await;
pool.clear().await;
assert_eq!(pool.count().await, 0);
}
#[tokio::test]
async fn test_pool_is_full() {
let pool = ConnectionPool::new(PoolConfig::new(2));
assert!(!pool.is_full().await);
pool.admit("c1", 1000).await;
assert!(!pool.is_full().await);
pool.admit("c2", 2000).await;
assert!(pool.is_full().await);
}
#[tokio::test]
async fn test_pool_lru_order_list() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await;
pool.admit("c3", 3000).await;
let order = pool.lru_order_list().await;
assert_eq!(order, vec!["c3", "c2", "c1"]);
pool.touch("c1", 4000).await;
let order2 = pool.lru_order_list().await;
assert_eq!(order2, vec!["c1", "c3", "c2"]);
}
#[tokio::test]
async fn test_pool_remove_updates_lru_order() {
let pool = ConnectionPool::new(PoolConfig::new(10));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await;
pool.admit("c3", 3000).await;
pool.remove("c2").await;
let order = pool.lru_order_list().await;
assert_eq!(order, vec!["c3", "c1"]);
}
#[tokio::test]
async fn test_pool_admit_after_evict_maintains_count() {
let pool = ConnectionPool::new(PoolConfig::new(1));
pool.admit("c1", 1000).await;
pool.admit("c2", 2000).await; pool.admit("c3", 3000).await; assert_eq!(pool.count().await, 1);
assert!(pool.get("c3").await.is_some());
}
}