1use std::collections::{HashMap, VecDeque};
13use std::sync::Arc;
14use tokio::sync::RwLock;
15
16#[derive(Debug, Clone, Copy)]
18pub struct PoolConfig {
19 pub max_connections: usize,
21}
22
23impl Default for PoolConfig {
24 fn default() -> Self {
25 Self {
26 max_connections: 10_000,
27 }
28 }
29}
30
31impl PoolConfig {
32 pub fn new(max_connections: usize) -> Self {
33 Self { max_connections }
34 }
35
36 pub fn validate(&self) -> Result<(), String> {
37 if self.max_connections == 0 {
38 return Err("max_connections must be > 0".to_string());
39 }
40 Ok(())
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct PooledConnection {
47 pub connection_id: String,
49 pub user_id: Option<i64>,
51 pub last_active_at: i64,
53 pub created_at: i64,
55 pub messages_sent: u64,
57 pub messages_received: u64,
59}
60
61impl PooledConnection {
62 pub fn new(connection_id: impl Into<String>, now_ms: i64) -> Self {
63 Self {
64 connection_id: connection_id.into(),
65 user_id: None,
66 last_active_at: now_ms,
67 created_at: now_ms,
68 messages_sent: 0,
69 messages_received: 0,
70 }
71 }
72
73 pub fn with_user(mut self, user_id: i64) -> Self {
74 self.user_id = Some(user_id);
75 self
76 }
77
78 pub fn touch(&mut self, now_ms: i64) {
80 self.last_active_at = now_ms;
81 }
82
83 pub fn record_sent(&mut self) {
85 self.messages_sent += 1;
86 }
87
88 pub fn record_received(&mut self) {
90 self.messages_received += 1;
91 }
92
93 pub fn idle_ms(&self, now_ms: i64) -> i64 {
95 now_ms - self.last_active_at
96 }
97
98 pub fn uptime_ms(&self, now_ms: i64) -> i64 {
100 now_ms - self.created_at
101 }
102}
103
104#[derive(Debug)]
106pub struct ConnectionPool {
107 config: PoolConfig,
108 connections: Arc<RwLock<HashMap<String, PooledConnection>>>,
110 lru_order: Arc<RwLock<VecDeque<String>>>,
112}
113
114#[derive(Debug, PartialEq, Eq)]
116pub enum AdmitResult {
117 Admitted,
119 AlreadyExists,
121 EvictedAndAdmitted { evicted_id: String },
123}
124
125impl ConnectionPool {
126 pub fn new(config: PoolConfig) -> Self {
127 Self {
128 config,
129 connections: Arc::new(RwLock::new(HashMap::new())),
130 lru_order: Arc::new(RwLock::new(VecDeque::new())),
131 }
132 }
133
134 pub fn config(&self) -> &PoolConfig {
136 &self.config
137 }
138
139 pub async fn admit(&self, connection_id: impl Into<String>, now_ms: i64) -> AdmitResult {
144 let id = connection_id.into();
145 let mut connections = self.connections.write().await;
146 if connections.contains_key(&id) {
147 return AdmitResult::AlreadyExists;
148 }
149
150 let mut lru = self.lru_order.write().await;
151 let evicted = if connections.len() >= self.config.max_connections {
153 let mut evicted_id = None;
155 while let Some(candidate) = lru.pop_back() {
156 if connections.contains_key(&candidate) {
157 connections.remove(&candidate);
158 evicted_id = Some(candidate);
159 break;
160 }
161 }
162 evicted_id
163 } else {
164 None
165 };
166
167 connections.insert(id.clone(), PooledConnection::new(&id, now_ms));
169 lru.push_front(id.clone());
170
171 match evicted {
172 Some(evicted_id) => AdmitResult::EvictedAndAdmitted { evicted_id },
173 None => AdmitResult::Admitted,
174 }
175 }
176
177 pub async fn remove(&self, connection_id: &str) -> Option<PooledConnection> {
179 let mut connections = self.connections.write().await;
180 let removed = connections.remove(connection_id);
181 if removed.is_some() {
182 let mut lru = self.lru_order.write().await;
183 lru.retain(|id| id != connection_id);
184 }
185 removed
186 }
187
188 pub async fn touch(&self, connection_id: &str, now_ms: i64) -> bool {
190 let mut connections = self.connections.write().await;
191 if let Some(conn) = connections.get_mut(connection_id) {
192 conn.touch(now_ms);
193 drop(connections);
194 let mut lru = self.lru_order.write().await;
195 lru.retain(|id| id != connection_id);
196 lru.push_front(connection_id.to_string());
197 return true;
198 }
199 false
200 }
201
202 pub async fn record_sent(&self, connection_id: &str) -> bool {
204 let mut connections = self.connections.write().await;
205 if let Some(conn) = connections.get_mut(connection_id) {
206 conn.record_sent();
207 return true;
208 }
209 false
210 }
211
212 pub async fn record_received(&self, connection_id: &str) -> bool {
214 let mut connections = self.connections.write().await;
215 if let Some(conn) = connections.get_mut(connection_id) {
216 conn.record_received();
217 return true;
218 }
219 false
220 }
221
222 pub async fn get(&self, connection_id: &str) -> Option<PooledConnection> {
224 let connections = self.connections.read().await;
225 connections.get(connection_id).cloned()
226 }
227
228 pub async fn count(&self) -> usize {
230 let connections = self.connections.read().await;
231 connections.len()
232 }
233
234 pub async fn is_full(&self) -> bool {
236 self.count().await >= self.config.max_connections
237 }
238
239 pub async fn find_by_user(&self, user_id: i64) -> Vec<PooledConnection> {
241 let connections = self.connections.read().await;
242 let mut result: Vec<PooledConnection> = connections
243 .values()
244 .filter(|c| c.user_id == Some(user_id))
245 .cloned()
246 .collect();
247 result.sort_by(|a, b| a.connection_id.cmp(&b.connection_id));
248 result
249 }
250
251 pub async fn evict_idle(&self, idle_threshold_ms: i64, now_ms: i64) -> usize {
253 let mut connections = self.connections.write().await;
254 let mut lru = self.lru_order.write().await;
255 let before = connections.len();
256 let to_remove: Vec<String> = connections
257 .iter()
258 .filter(|(_, c)| c.idle_ms(now_ms) >= idle_threshold_ms)
259 .map(|(id, _)| id.clone())
260 .collect();
261 for id in &to_remove {
262 connections.remove(id);
263 }
264 lru.retain(|id| !to_remove.contains(id));
265 before - connections.len()
266 }
267
268 pub async fn clear(&self) {
270 let mut connections = self.connections.write().await;
271 let mut lru = self.lru_order.write().await;
272 connections.clear();
273 lru.clear();
274 }
275
276 pub async fn lru_order_list(&self) -> Vec<String> {
278 let lru = self.lru_order.read().await;
279 lru.iter().cloned().collect()
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_pool_config_default() {
289 let cfg = PoolConfig::default();
290 assert_eq!(cfg.max_connections, 10_000);
291 }
292
293 #[test]
294 fn test_pool_config_validate_ok() {
295 let cfg = PoolConfig::new(100);
296 assert!(cfg.validate().is_ok());
297 }
298
299 #[test]
300 fn test_pool_config_validate_zero() {
301 let cfg = PoolConfig::new(0);
302 assert!(cfg.validate().is_err());
303 }
304
305 #[test]
306 fn test_pooled_connection_new() {
307 let conn = PooledConnection::new("c1", 1000);
308 assert_eq!(conn.connection_id, "c1");
309 assert!(conn.user_id.is_none());
310 assert_eq!(conn.last_active_at, 1000);
311 assert_eq!(conn.created_at, 1000);
312 assert_eq!(conn.messages_sent, 0);
313 assert_eq!(conn.messages_received, 0);
314 }
315
316 #[test]
317 fn test_pooled_connection_with_user() {
318 let conn = PooledConnection::new("c1", 1000).with_user(42);
319 assert_eq!(conn.user_id, Some(42));
320 }
321
322 #[test]
323 fn test_pooled_connection_touch_updates_last_active() {
324 let mut conn = PooledConnection::new("c1", 1000);
325 conn.touch(2000);
326 assert_eq!(conn.last_active_at, 2000);
327 }
328
329 #[test]
330 fn test_pooled_connection_record_sent_and_received() {
331 let mut conn = PooledConnection::new("c1", 1000);
332 conn.record_sent();
333 conn.record_sent();
334 conn.record_received();
335 assert_eq!(conn.messages_sent, 2);
336 assert_eq!(conn.messages_received, 1);
337 }
338
339 #[test]
340 fn test_pooled_connection_idle_ms() {
341 let conn = PooledConnection::new("c1", 1000);
342 assert_eq!(conn.idle_ms(1500), 500);
343 }
344
345 #[test]
346 fn test_pooled_connection_uptime_ms() {
347 let conn = PooledConnection::new("c1", 1000);
348 assert_eq!(conn.uptime_ms(3000), 2000);
349 }
350
351 #[tokio::test]
352 async fn test_pool_admit_new_connection() {
353 let pool = ConnectionPool::new(PoolConfig::new(10));
354 let result = pool.admit("c1", 1000).await;
355 assert_eq!(result, AdmitResult::Admitted);
356 assert_eq!(pool.count().await, 1);
357 }
358
359 #[tokio::test]
360 async fn test_pool_admit_duplicate_returns_already_exists() {
361 let pool = ConnectionPool::new(PoolConfig::new(10));
362 pool.admit("c1", 1000).await;
363 let result = pool.admit("c1", 2000).await;
364 assert_eq!(result, AdmitResult::AlreadyExists);
365 assert_eq!(pool.count().await, 1);
366 }
367
368 #[tokio::test]
369 async fn test_pool_admit_evicts_lru_when_full() {
370 let pool = ConnectionPool::new(PoolConfig::new(2));
371 pool.admit("c1", 1000).await;
372 pool.admit("c2", 2000).await;
373 let result = pool.admit("c3", 3000).await;
375 match result {
376 AdmitResult::EvictedAndAdmitted { evicted_id } => {
377 assert_eq!(evicted_id, "c1");
378 }
379 _ => panic!("expected EvictedAndAdmitted, got {:?}", result),
380 }
381 assert_eq!(pool.count().await, 2);
382 assert!(pool.get("c1").await.is_none());
383 assert!(pool.get("c2").await.is_some());
384 assert!(pool.get("c3").await.is_some());
385 }
386
387 #[tokio::test]
388 async fn test_pool_admit_touch_updates_lru_order() {
389 let pool = ConnectionPool::new(PoolConfig::new(2));
390 pool.admit("c1", 1000).await;
391 pool.admit("c2", 2000).await;
392 pool.touch("c1", 5000).await;
394 let result = pool.admit("c3", 6000).await;
396 match result {
397 AdmitResult::EvictedAndAdmitted { evicted_id } => {
398 assert_eq!(evicted_id, "c2");
399 }
400 _ => panic!("expected c2 to be evicted"),
401 }
402 }
403
404 #[tokio::test]
405 async fn test_pool_remove() {
406 let pool = ConnectionPool::new(PoolConfig::new(10));
407 pool.admit("c1", 1000).await;
408 let removed = pool.remove("c1").await;
409 assert!(removed.is_some());
410 assert_eq!(pool.count().await, 0);
411 }
412
413 #[tokio::test]
414 async fn test_pool_remove_missing_returns_none() {
415 let pool = ConnectionPool::new(PoolConfig::new(10));
416 assert!(pool.remove("ghost").await.is_none());
417 }
418
419 #[tokio::test]
420 async fn test_pool_touch_updates_last_active() {
421 let pool = ConnectionPool::new(PoolConfig::new(10));
422 pool.admit("c1", 1000).await;
423 pool.touch("c1", 5000).await;
424 let conn = pool.get("c1").await.unwrap();
425 assert_eq!(conn.last_active_at, 5000);
426 }
427
428 #[tokio::test]
429 async fn test_pool_touch_unknown_returns_false() {
430 let pool = ConnectionPool::new(PoolConfig::new(10));
431 assert!(!pool.touch("ghost", 1000).await);
432 }
433
434 #[tokio::test]
435 async fn test_pool_record_sent_and_received() {
436 let pool = ConnectionPool::new(PoolConfig::new(10));
437 pool.admit("c1", 1000).await;
438 assert!(pool.record_sent("c1").await);
439 assert!(pool.record_received("c1").await);
440 let conn = pool.get("c1").await.unwrap();
441 assert_eq!(conn.messages_sent, 1);
442 assert_eq!(conn.messages_received, 1);
443 }
444
445 #[tokio::test]
446 async fn test_pool_record_sent_unknown_returns_false() {
447 let pool = ConnectionPool::new(PoolConfig::new(10));
448 assert!(!pool.record_sent("ghost").await);
449 }
450
451 #[tokio::test]
452 async fn test_pool_find_by_user() {
453 let pool = ConnectionPool::new(PoolConfig::new(10));
454 pool.admit("c1", 1000).await;
455 pool.admit("c2", 1000).await;
456 {
458 let mut conns = pool.connections.write().await;
459 conns.get_mut("c1").unwrap().user_id = Some(100);
460 conns.get_mut("c2").unwrap().user_id = Some(200);
461 }
462 let found = pool.find_by_user(100).await;
463 assert_eq!(found.len(), 1);
464 assert_eq!(found[0].connection_id, "c1");
465 }
466
467 #[tokio::test]
468 async fn test_pool_find_by_user_none() {
469 let pool = ConnectionPool::new(PoolConfig::new(10));
470 pool.admit("c1", 1000).await;
471 let found = pool.find_by_user(999).await;
472 assert!(found.is_empty());
473 }
474
475 #[tokio::test]
476 async fn test_pool_evict_idle() {
477 let pool = ConnectionPool::new(PoolConfig::new(10));
478 pool.admit("c1", 1000).await;
479 pool.admit("c2", 2000).await;
480 pool.admit("c3", 5000).await;
481 let evicted = pool.evict_idle(3000, 6000).await;
484 assert_eq!(evicted, 2); assert_eq!(pool.count().await, 1);
486 assert!(pool.get("c3").await.is_some());
487 }
488
489 #[tokio::test]
490 async fn test_pool_evict_idle_none() {
491 let pool = ConnectionPool::new(PoolConfig::new(10));
492 pool.admit("c1", 1000).await;
493 let evicted = pool.evict_idle(100_000, 2000).await;
495 assert_eq!(evicted, 0);
496 }
497
498 #[tokio::test]
499 async fn test_pool_clear() {
500 let pool = ConnectionPool::new(PoolConfig::new(10));
501 pool.admit("c1", 1000).await;
502 pool.admit("c2", 2000).await;
503 pool.clear().await;
504 assert_eq!(pool.count().await, 0);
505 }
506
507 #[tokio::test]
508 async fn test_pool_is_full() {
509 let pool = ConnectionPool::new(PoolConfig::new(2));
510 assert!(!pool.is_full().await);
511 pool.admit("c1", 1000).await;
512 assert!(!pool.is_full().await);
513 pool.admit("c2", 2000).await;
514 assert!(pool.is_full().await);
515 }
516
517 #[tokio::test]
518 async fn test_pool_lru_order_list() {
519 let pool = ConnectionPool::new(PoolConfig::new(10));
520 pool.admit("c1", 1000).await;
521 pool.admit("c2", 2000).await;
522 pool.admit("c3", 3000).await;
523 let order = pool.lru_order_list().await;
525 assert_eq!(order, vec!["c3", "c2", "c1"]);
526 pool.touch("c1", 4000).await;
528 let order2 = pool.lru_order_list().await;
529 assert_eq!(order2, vec!["c1", "c3", "c2"]);
530 }
531
532 #[tokio::test]
533 async fn test_pool_remove_updates_lru_order() {
534 let pool = ConnectionPool::new(PoolConfig::new(10));
535 pool.admit("c1", 1000).await;
536 pool.admit("c2", 2000).await;
537 pool.admit("c3", 3000).await;
538 pool.remove("c2").await;
539 let order = pool.lru_order_list().await;
540 assert_eq!(order, vec!["c3", "c1"]);
541 }
542
543 #[tokio::test]
544 async fn test_pool_admit_after_evict_maintains_count() {
545 let pool = ConnectionPool::new(PoolConfig::new(1));
546 pool.admit("c1", 1000).await;
547 pool.admit("c2", 2000).await; pool.admit("c3", 3000).await; assert_eq!(pool.count().await, 1);
550 assert!(pool.get("c3").await.is_some());
551 }
552}