1use dashmap::DashMap;
18use log::{debug, warn};
19use parking_lot::Mutex;
20use pingora_timeout::{sleep, timeout};
21use std::collections::HashMap;
22use std::io;
23use std::sync::Arc;
24use std::time::Duration;
25use tokio::io::{AsyncRead, AsyncReadExt};
26use tokio::sync::{oneshot, watch, Notify, OwnedMutexGuard};
27
28use super::lru::Lru;
29use futures::FutureExt;
30
31type GroupKey = u64;
32#[cfg(unix)]
33type ID = i32;
34#[cfg(windows)]
35type ID = usize;
36
37#[derive(Clone, Debug)]
39pub struct ConnectionMeta {
40 pub key: GroupKey,
42 pub id: ID,
44}
45
46impl ConnectionMeta {
47 pub fn new(key: GroupKey, id: ID) -> Self {
49 ConnectionMeta { key, id }
50 }
51}
52
53struct PoolConnection<S> {
54 pub notify_use: oneshot::Sender<bool>,
55 pub connection: S,
56}
57
58impl<S> PoolConnection<S> {
59 pub fn new(notify_use: oneshot::Sender<bool>, connection: S) -> Self {
60 PoolConnection {
61 notify_use,
62 connection,
63 }
64 }
65
66 pub fn release(self) -> S {
67 let _ = self.notify_use.send(true);
69 self.connection
71 }
72}
73
74use crossbeam_queue::ArrayQueue;
75
76pub struct PoolNode<T> {
78 connections: Mutex<HashMap<ID, T>>,
79 hot_queue: ArrayQueue<(ID, T)>,
81 hot_queue_remove_lock: Mutex<()>,
83 }
85
86const HOT_QUEUE_SIZE: usize = 16;
88
89impl<T> PoolNode<T> {
90 pub fn new() -> Self {
92 PoolNode {
93 connections: Mutex::new(HashMap::new()),
94 hot_queue: ArrayQueue::new(HOT_QUEUE_SIZE),
95 hot_queue_remove_lock: Mutex::new(()),
96 }
97 }
98
99 pub fn get_any(&self) -> Option<(ID, T)> {
101 let hot_conn = self.hot_queue.pop();
102 if hot_conn.is_some() {
103 return hot_conn;
104 }
105 let mut connections = self.connections.lock();
106 let id = match connections.iter().next() {
108 Some((k, _)) => *k, None => return None,
110 };
111 let connection = connections.remove(&id).unwrap();
113 Some((id, connection))
117 }
119
120 pub fn insert(&self, id: ID, conn: T) {
122 if let Err(node) = self.hot_queue.push((id, conn)) {
123 let mut connections = self.connections.lock();
125 connections.insert(node.0, node.1); }
127 }
128
129 pub fn is_empty(&self) -> bool {
143 self.hot_queue.is_empty() && self.connections.lock().is_empty()
146 }
147
148 pub fn remove(&self, id: ID) -> Option<T> {
153 let removed = self.connections.lock().remove(&id);
155 if removed.is_some() {
156 return removed;
157 } let _queue_lock = self.hot_queue_remove_lock.lock();
160 let max_len = self.hot_queue.len();
162 for _ in 0..max_len {
163 if let Some((conn_id, conn)) = self.hot_queue.pop() {
164 if conn_id == id {
165 return Some(conn);
167 } else {
168 self.insert(conn_id, conn);
170 }
171 } else {
172 return None;
174 }
175 }
176 None
177 }
179}
180
181type Pool<S> = PoolNode<PoolConnection<S>>;
182
183pub struct ConnectionPool<S> {
188 pools: DashMap<GroupKey, Arc<Pool<S>>>,
190 lru: Lru<ID, ConnectionMeta>,
191}
192
193impl<S> ConnectionPool<S> {
194 pub fn new(size: usize) -> Self {
199 ConnectionPool {
200 pools: DashMap::with_capacity(size),
201 lru: Lru::new(size),
202 }
203 }
204
205 fn insert_pool_connection(&self, key: GroupKey, id: ID, connection: PoolConnection<S>) {
210 let pool_node = self
211 .pools
212 .entry(key)
213 .or_insert_with(|| Arc::new(PoolNode::new()));
214 pool_node.insert(id, connection);
215 }
216
217 fn try_remove_empty_node(&self, key: GroupKey) {
233 if let Some(node) = self.pools.get(&key) {
234 if node.is_empty() {
235 drop(node);
240 self.pools.remove_if(&key, |_, node| node.is_empty());
241 }
242 }
243 }
244
245 fn pop_evicted(&self, meta: &ConnectionMeta) {
247 let pool_node = match self.pools.get(&meta.key) {
248 Some(v) => v.value().clone(),
249 None => {
250 warn!("Fail to get pool node for {meta:?}");
251 return;
252 } };
254
255 pool_node.remove(meta.id);
256 debug!("evict fd: {} from key {}", meta.id, meta.key);
257
258 if pool_node.is_empty() {
263 self.try_remove_empty_node(meta.key);
264 }
265 }
266
267 pub fn pop_closed(&self, meta: &ConnectionMeta) {
268 self.pop_evicted(meta);
270 self.lru.pop(&meta.id);
271 }
272
273 pub fn get(&self, key: &GroupKey) -> Option<S> {
275 let pool_node = self.pools.get(key)?.value().clone();
276
277 if let Some((id, connection)) = pool_node.get_any() {
278 self.lru.pop(&id); if pool_node.is_empty() {
285 self.try_remove_empty_node(*key);
286 }
287
288 Some(connection.release())
289 } else {
290 self.try_remove_empty_node(*key);
292 None
293 }
294 }
295
296 pub fn put(
301 &self,
302 meta: &ConnectionMeta,
303 connection: S,
304 ) -> (Arc<Notify>, oneshot::Receiver<bool>) {
305 let (notify_close, evicted) = self.lru.add(meta.id, meta.clone());
306 for meta in &evicted {
307 self.pop_evicted(meta);
308 }
309 let (notify_use, watch_use) = oneshot::channel();
310 let connection = PoolConnection::new(notify_use, connection);
311 self.insert_pool_connection(meta.key, meta.id, connection);
312 (notify_close, watch_use)
313 }
314
315 pub async fn idle_poll<Stream>(
324 &self,
325 connection: OwnedMutexGuard<Stream>,
326 meta: &ConnectionMeta,
327 timeout: Option<Duration>,
328 notify_evicted: Arc<Notify>,
329 watch_use: oneshot::Receiver<bool>,
330 ) -> bool
331 where
332 Stream: AsyncRead + Unpin + Send,
333 {
334 let evicted = notify_evicted.notified();
338 tokio::pin!(evicted);
339
340 let read_result = tokio::select! {
341 biased;
342 event = watch_use => {
343 return match event {
344 Ok(_) => {
345 debug!("idle connection is being picked up");
346 false
347 }
348 Err(_) => evicted.now_or_never().is_some(),
355 };
356 },
357 _ = &mut evicted => {
358 debug!("idle connection is being evicted");
359 return true
361 },
362 read_result = read_with_timeout(connection , timeout) => read_result
363 };
364
365 match read_result {
366 Ok(n) => {
367 if n > 0 {
368 warn!("Data received on idle client connection, close it");
369 } else {
370 debug!("Peer closed the idle connection or timeout");
371 }
372 }
373
374 Err(e) => {
375 debug!("error with the idle connection, close it {:?}", e);
376 }
377 };
378 self.pop_closed(meta);
380 false
381 }
382
383 pub async fn idle_timeout(
390 &self,
391 meta: &ConnectionMeta,
392 timeout: Option<Duration>,
393 notify_evicted: Arc<Notify>,
394 mut notify_closed: watch::Receiver<bool>,
395 watch_use: oneshot::Receiver<bool>,
396 ) -> bool {
397 let evicted = notify_evicted.notified();
401 tokio::pin!(evicted);
402
403 tokio::select! {
404 biased;
405 event = watch_use => {
406 match event {
407 Ok(_) => {
408 debug!("idle connection is being picked up");
409 false
410 }
411 Err(_) => evicted.now_or_never().is_some(),
418 }
419 },
420 _ = &mut evicted => {
421 debug!("idle connection is being evicted");
422 true
424 },
425 _ = notify_closed.changed() => {
426 debug!("idle connection is being closed");
428 self.pop_closed(meta);
429 false
430 }
431 _ = sleep(timeout.unwrap_or(Duration::MAX)), if timeout.is_some() => {
433 debug!("idle connection is being evicted");
434 self.pop_closed(meta);
435 false
436 }
437 }
438 }
439}
440
441async fn read_with_timeout<S>(
442 mut connection: OwnedMutexGuard<S>,
443 timeout_duration: Option<Duration>,
444) -> io::Result<usize>
445where
446 S: AsyncRead + Unpin + Send,
447{
448 let mut buf = [0; 1];
449 let read_event = connection.read(&mut buf[..]);
450 match timeout_duration {
451 Some(d) => match timeout(d, read_event).await {
452 Ok(res) => res,
453 Err(e) => {
454 debug!("keepalive timeout {:?} reached, {:?}", d, e);
455 Ok(0)
456 }
457 },
458 _ => read_event.await,
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use log::debug;
466 use tokio::sync::Mutex as AsyncMutex;
467 use tokio_test::io::{Builder, Mock};
468
469 fn pool_len<S>(pool: &ConnectionPool<S>) -> usize {
470 pool.pools.len()
471 }
472
473 fn pool_contains<S>(pool: &ConnectionPool<S>, key: GroupKey) -> bool {
474 pool.pools.contains_key(&key)
475 }
476
477 #[tokio::test]
478 async fn test_lookup() {
479 let meta1 = ConnectionMeta::new(101, 1);
480 let value1 = "v1".to_string();
481 let meta2 = ConnectionMeta::new(102, 2);
482 let value2 = "v2".to_string();
483 let meta3 = ConnectionMeta::new(101, 3);
484 let value3 = "v3".to_string();
485 let cp: ConnectionPool<String> = ConnectionPool::new(3); cp.put(&meta1, value1.clone());
487 cp.put(&meta2, value2.clone());
488 cp.put(&meta3, value3.clone());
489
490 let found_b = cp.get(&meta2.key).unwrap();
491 assert_eq!(found_b, value2);
492
493 let found_a1 = cp.get(&meta1.key).unwrap();
494 let found_a2 = cp.get(&meta1.key).unwrap();
495
496 assert!(
497 found_a1 == value1 && found_a2 == value3 || found_a2 == value1 && found_a1 == value3
498 );
499 }
500
501 #[tokio::test]
502 async fn test_pop() {
503 let meta1 = ConnectionMeta::new(101, 1);
504 let value1 = "v1".to_string();
505 let meta2 = ConnectionMeta::new(102, 2);
506 let value2 = "v2".to_string();
507 let meta3 = ConnectionMeta::new(101, 3);
508 let value3 = "v3".to_string();
509 let cp: ConnectionPool<String> = ConnectionPool::new(3); cp.put(&meta1, value1);
511 cp.put(&meta2, value2);
512 cp.put(&meta3, value3.clone());
513
514 cp.pop_closed(&meta1);
515
516 let found_a1 = cp.get(&meta1.key).unwrap();
517 assert_eq!(found_a1, value3);
518
519 cp.pop_closed(&meta1);
520 assert!(cp.get(&meta1.key).is_none())
521 }
522
523 #[tokio::test]
524 async fn test_eviction() {
525 let meta1 = ConnectionMeta::new(101, 1);
526 let value1 = "v1".to_string();
527 let meta2 = ConnectionMeta::new(102, 2);
528 let value2 = "v2".to_string();
529 let meta3 = ConnectionMeta::new(101, 3);
530 let value3 = "v3".to_string();
531 let cp: ConnectionPool<String> = ConnectionPool::new(2);
532 let (notify_close1, _) = cp.put(&meta1, value1.clone());
533 let (notify_close2, _) = cp.put(&meta2, value2.clone());
534 let (notify_close3, _) = cp.put(&meta3, value3.clone()); let closed_item = tokio::select! {
537 _ = notify_close1.notified() => {debug!("notifier1"); 1},
538 _ = notify_close2.notified() => {debug!("notifier2"); 2},
539 _ = notify_close3.notified() => {debug!("notifier3"); 3},
540 };
541 assert_eq!(closed_item, 1);
542
543 let found_a1 = cp.get(&meta1.key).unwrap();
544 assert_eq!(found_a1, value3);
545 assert_eq!(cp.get(&meta1.key), None)
546 }
547
548 #[tokio::test]
549 #[should_panic(expected = "There is still data left to read.")]
550 async fn test_read_close() {
551 let meta1 = ConnectionMeta::new(101, 1);
552 let mock_io1 = Arc::new(AsyncMutex::new(Builder::new().read(b"garbage").build()));
553 let meta2 = ConnectionMeta::new(102, 2);
554 let mock_io2 = Arc::new(AsyncMutex::new(
555 Builder::new().wait(Duration::from_secs(99)).build(),
556 ));
557 let meta3 = ConnectionMeta::new(101, 3);
558 let mock_io3 = Arc::new(AsyncMutex::new(
559 Builder::new().wait(Duration::from_secs(99)).build(),
560 ));
561 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(3);
562 let (c1, u1) = cp.put(&meta1, mock_io1.clone());
563 let (c2, u2) = cp.put(&meta2, mock_io2.clone());
564 let (c3, u3) = cp.put(&meta3, mock_io3.clone());
565
566 let closed_item = tokio::select! {
567 _ = cp.idle_poll(mock_io1.try_lock_owned().unwrap(), &meta1, None, c1, u1) => {debug!("notifier1"); 1},
568 _ = cp.idle_poll(mock_io2.try_lock_owned().unwrap(), &meta2, None, c2, u2) => {debug!("notifier2"); 2},
569 _ = cp.idle_poll(mock_io3.try_lock_owned().unwrap(), &meta3, None, c3, u3) => {debug!("notifier3"); 3},
570 };
571 assert_eq!(closed_item, 1);
572
573 let _ = cp.get(&meta1.key).unwrap(); assert!(cp.get(&meta1.key).is_none()) }
576
577 #[tokio::test]
578 async fn test_read_timeout() {
579 let meta1 = ConnectionMeta::new(101, 1);
580 let mock_io1 = Arc::new(AsyncMutex::new(
581 Builder::new().wait(Duration::from_secs(99)).build(),
582 ));
583 let meta2 = ConnectionMeta::new(102, 2);
584 let mock_io2 = Arc::new(AsyncMutex::new(
585 Builder::new().wait(Duration::from_secs(99)).build(),
586 ));
587 let meta3 = ConnectionMeta::new(101, 3);
588 let mock_io3 = Arc::new(AsyncMutex::new(
589 Builder::new().wait(Duration::from_secs(99)).build(),
590 ));
591 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(3);
592 let (c1, u1) = cp.put(&meta1, mock_io1.clone());
593 let (c2, u2) = cp.put(&meta2, mock_io2.clone());
594 let (c3, u3) = cp.put(&meta3, mock_io3.clone());
595
596 let closed_item = tokio::select! {
597 _ = cp.idle_poll(mock_io1.try_lock_owned().unwrap(), &meta1, Some(Duration::from_secs(1)), c1, u1) => {debug!("notifier1"); 1},
598 _ = cp.idle_poll(mock_io2.try_lock_owned().unwrap(), &meta2, Some(Duration::from_secs(2)), c2, u2) => {debug!("notifier2"); 2},
599 _ = cp.idle_poll(mock_io3.try_lock_owned().unwrap(), &meta3, Some(Duration::from_secs(3)), c3, u3) => {debug!("notifier3"); 3},
600 };
601 assert_eq!(closed_item, 1);
602
603 let _ = cp.get(&meta1.key).unwrap(); assert!(cp.get(&meta1.key).is_none()) }
606
607 #[tokio::test]
608 async fn test_evict_poll() {
609 let meta1 = ConnectionMeta::new(101, 1);
610 let mock_io1 = Arc::new(AsyncMutex::new(
611 Builder::new().wait(Duration::from_secs(99)).build(),
612 ));
613 let meta2 = ConnectionMeta::new(102, 2);
614 let mock_io2 = Arc::new(AsyncMutex::new(
615 Builder::new().wait(Duration::from_secs(99)).build(),
616 ));
617 let meta3 = ConnectionMeta::new(101, 3);
618 let mock_io3 = Arc::new(AsyncMutex::new(
619 Builder::new().wait(Duration::from_secs(99)).build(),
620 ));
621 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(2);
622 let (c1, u1) = cp.put(&meta1, mock_io1.clone());
623 let (c2, u2) = cp.put(&meta2, mock_io2.clone());
624 let (c3, u3) = cp.put(&meta3, mock_io3.clone()); let closed_item = tokio::select! {
627 _ = cp.idle_poll(mock_io1.try_lock_owned().unwrap(), &meta1, None, c1, u1) => {debug!("notifier1"); 1},
628 _ = cp.idle_poll(mock_io2.try_lock_owned().unwrap(), &meta2, None, c2, u2) => {debug!("notifier2"); 2},
629 _ = cp.idle_poll(mock_io3.try_lock_owned().unwrap(), &meta3, None, c3, u3) => {debug!("notifier3"); 3},
630 };
631 assert_eq!(closed_item, 1);
632
633 let _ = cp.get(&meta1.key).unwrap(); assert!(cp.get(&meta1.key).is_none()) }
636
637 #[tokio::test]
638 async fn test_idle_poll_reports_notify_evicted() {
639 let meta1 = ConnectionMeta::new(101, 1);
640 let mock_io1 = Arc::new(AsyncMutex::new(
641 Builder::new().wait(Duration::from_secs(99)).build(),
642 ));
643 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
644
645 let (notify_evicted, watch_use) = cp.put(&meta1, mock_io1.clone());
646 notify_evicted.notify_one();
647
648 let evicted = cp
649 .idle_poll(
650 mock_io1.try_lock_owned().unwrap(),
651 &meta1,
652 None,
653 notify_evicted,
654 watch_use,
655 )
656 .await;
657
658 assert!(evicted, "notify_evicted should report eviction");
659 }
660
661 #[tokio::test]
662 async fn test_idle_poll_reports_lru_eviction_after_pool_remove() {
663 let meta1 = ConnectionMeta::new(101, 1);
664 let mock_io1 = Arc::new(AsyncMutex::new(
665 Builder::new().wait(Duration::from_secs(99)).build(),
666 ));
667 let meta2 = ConnectionMeta::new(202, 2);
668 let mock_io2 = Arc::new(AsyncMutex::new(
669 Builder::new().wait(Duration::from_secs(99)).build(),
670 ));
671 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
672
673 let (notify_evicted, watch_use) = cp.put(&meta1, mock_io1.clone());
674 cp.put(&meta2, mock_io2);
675
676 let evicted = cp
677 .idle_poll(
678 mock_io1.try_lock_owned().unwrap(),
679 &meta1,
680 None,
681 notify_evicted,
682 watch_use,
683 )
684 .await;
685
686 assert!(evicted, "LRU eviction should report eviction");
687 }
688
689 #[tokio::test]
690 async fn test_idle_poll_reports_sender_drop_without_notify_not_evicted() {
691 let meta = ConnectionMeta::new(101, 1);
692 let mock_io = Arc::new(AsyncMutex::new(
693 Builder::new().wait(Duration::from_secs(99)).build(),
694 ));
695 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
696
697 let (notify_evicted, watch_use) = cp.put(&meta, mock_io.clone());
698 cp.pop_closed(&meta);
699
700 let evicted = cp
701 .idle_poll(
702 mock_io.try_lock_owned().unwrap(),
703 &meta,
704 None,
705 notify_evicted,
706 watch_use,
707 )
708 .await;
709
710 assert!(
711 !evicted,
712 "sender drop without notify should not report eviction"
713 );
714 }
715
716 #[tokio::test]
717 async fn test_idle_poll_reports_reuse_not_evicted() {
718 let meta = ConnectionMeta::new(101, 1);
719 let mock_io = Arc::new(AsyncMutex::new(
720 Builder::new().wait(Duration::from_secs(99)).build(),
721 ));
722 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
723
724 let (notify_evicted, watch_use) = cp.put(&meta, mock_io.clone());
725 assert!(cp.get(&meta.key).is_some());
726
727 let evicted = cp
728 .idle_poll(
729 mock_io.try_lock_owned().unwrap(),
730 &meta,
731 None,
732 notify_evicted,
733 watch_use,
734 )
735 .await;
736
737 assert!(!evicted, "reused connection should not report eviction");
738 }
739
740 #[tokio::test]
741 async fn test_idle_poll_reports_peer_close_not_evicted() {
742 let meta = ConnectionMeta::new(101, 1);
743 let mock_io = Arc::new(AsyncMutex::new(Builder::new().read(b"").build()));
744 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
745
746 let (notify_evicted, watch_use) = cp.put(&meta, mock_io.clone());
747
748 let evicted = cp
749 .idle_poll(
750 mock_io.try_lock_owned().unwrap(),
751 &meta,
752 None,
753 notify_evicted,
754 watch_use,
755 )
756 .await;
757
758 assert!(!evicted, "peer close should not report eviction");
759 assert!(cp.get(&meta.key).is_none());
760 }
761
762 #[tokio::test]
763 async fn test_idle_poll_reports_unexpected_data_not_evicted() {
764 let meta = ConnectionMeta::new(101, 1);
765 let mock_io = Arc::new(AsyncMutex::new(Builder::new().read(b"x").build()));
766 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
767
768 let (notify_evicted, watch_use) = cp.put(&meta, mock_io.clone());
769
770 let evicted = cp
771 .idle_poll(
772 mock_io.try_lock_owned().unwrap(),
773 &meta,
774 None,
775 notify_evicted,
776 watch_use,
777 )
778 .await;
779
780 assert!(!evicted, "unexpected data should not report eviction");
781 assert!(cp.get(&meta.key).is_none());
782 }
783
784 #[tokio::test]
785 async fn test_idle_poll_reports_read_error_not_evicted() {
786 let meta = ConnectionMeta::new(101, 1);
787 let mock_io = Arc::new(AsyncMutex::new(
788 Builder::new()
789 .read_error(io::Error::other("read failed"))
790 .build(),
791 ));
792 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
793
794 let (notify_evicted, watch_use) = cp.put(&meta, mock_io.clone());
795
796 let evicted = cp
797 .idle_poll(
798 mock_io.try_lock_owned().unwrap(),
799 &meta,
800 None,
801 notify_evicted,
802 watch_use,
803 )
804 .await;
805
806 assert!(!evicted, "read error should not report eviction");
807 assert!(cp.get(&meta.key).is_none());
808 }
809
810 #[tokio::test]
811 async fn test_idle_poll_reports_timeout_not_evicted() {
812 let meta = ConnectionMeta::new(101, 1);
813 let mock_io = Arc::new(AsyncMutex::new(
814 Builder::new().wait(Duration::from_secs(99)).build(),
815 ));
816 let cp: ConnectionPool<Arc<AsyncMutex<Mock>>> = ConnectionPool::new(1);
817
818 let (notify_evicted, watch_use) = cp.put(&meta, mock_io.clone());
819
820 let evicted = cp
821 .idle_poll(
822 mock_io.try_lock_owned().unwrap(),
823 &meta,
824 Some(Duration::from_millis(10)),
825 notify_evicted,
826 watch_use,
827 )
828 .await;
829
830 assert!(!evicted, "idle poll timeout should not report eviction");
831 assert!(cp.get(&meta.key).is_none());
832 }
833
834 #[tokio::test]
835 async fn test_idle_timeout_reports_timeout_not_evicted() {
836 let meta = ConnectionMeta::new(101, 1);
837 let cp: ConnectionPool<String> = ConnectionPool::new(1);
838 let (notify_evicted, watch_use) = cp.put(&meta, "v1".to_string());
839 let (_notify_closed, notify_closed_rx) = watch::channel(false);
840
841 let evicted = cp
842 .idle_timeout(
843 &meta,
844 Some(Duration::from_millis(10)),
845 notify_evicted,
846 notify_closed_rx,
847 watch_use,
848 )
849 .await;
850
851 assert!(!evicted, "idle timeout should not report eviction");
852 assert!(cp.get(&meta.key).is_none());
853 }
854
855 #[tokio::test]
856 async fn test_idle_timeout_reports_reuse_not_evicted() {
857 let meta = ConnectionMeta::new(101, 1);
858 let cp: ConnectionPool<String> = ConnectionPool::new(1);
859 let (notify_evicted, watch_use) = cp.put(&meta, "v1".to_string());
860 let (_notify_closed, notify_closed_rx) = watch::channel(false);
861
862 assert_eq!(cp.get(&meta.key), Some("v1".to_string()));
863
864 let evicted = cp
865 .idle_timeout(&meta, None, notify_evicted, notify_closed_rx, watch_use)
866 .await;
867
868 assert!(!evicted, "reused connection should not report eviction");
869 }
870
871 #[tokio::test]
872 async fn test_idle_timeout_reports_notify_evicted() {
873 let meta = ConnectionMeta::new(101, 1);
874 let cp: ConnectionPool<String> = ConnectionPool::new(1);
875 let (notify_evicted, watch_use) = cp.put(&meta, "v1".to_string());
876 let (_notify_closed, notify_closed_rx) = watch::channel(false);
877
878 notify_evicted.notify_one();
879
880 let evicted = cp
881 .idle_timeout(&meta, None, notify_evicted, notify_closed_rx, watch_use)
882 .await;
883
884 assert!(evicted, "notify_evicted should report eviction");
885 }
886
887 #[tokio::test]
888 async fn test_idle_timeout_reports_lru_eviction_after_pool_remove() {
889 let meta1 = ConnectionMeta::new(101, 1);
890 let meta2 = ConnectionMeta::new(202, 2);
891 let cp: ConnectionPool<String> = ConnectionPool::new(1);
892 let (notify_evicted, watch_use) = cp.put(&meta1, "v1".to_string());
893 let (_notify_closed, notify_closed_rx) = watch::channel(false);
894
895 cp.put(&meta2, "v2".to_string());
896
897 let evicted = cp
898 .idle_timeout(&meta1, None, notify_evicted, notify_closed_rx, watch_use)
899 .await;
900
901 assert!(evicted, "LRU eviction should report eviction");
902 }
903
904 #[tokio::test]
905 async fn test_idle_timeout_reports_lru_eviction_after_notify_registered() {
906 let meta1 = ConnectionMeta::new(101, 1);
907 let meta2 = ConnectionMeta::new(202, 2);
908 let cp = Arc::new(ConnectionPool::new(1));
909 let (notify_evicted, watch_use) = cp.put(&meta1, "v1".to_string());
910 let (_notify_closed, notify_closed_rx) = watch::channel(false);
911
912 let idle_cp = cp.clone();
913 let idle_meta = meta1.clone();
914 let idle_task = tokio::spawn(async move {
915 idle_cp
916 .idle_timeout(
917 &idle_meta,
918 None,
919 notify_evicted,
920 notify_closed_rx,
921 watch_use,
922 )
923 .await
924 });
925
926 tokio::task::yield_now().await;
927 cp.put(&meta2, "v2".to_string());
928
929 assert!(
930 idle_task.await.unwrap(),
931 "LRU eviction should report eviction after notify future was registered"
932 );
933 }
934
935 #[tokio::test]
936 async fn test_idle_timeout_reports_sender_drop_without_notify_not_evicted() {
937 let meta = ConnectionMeta::new(101, 1);
938 let cp: ConnectionPool<String> = ConnectionPool::new(1);
939 let (notify_evicted, watch_use) = cp.put(&meta, "v1".to_string());
940 let (_notify_closed, notify_closed_rx) = watch::channel(false);
941
942 cp.pop_closed(&meta);
943
944 let evicted = cp
945 .idle_timeout(&meta, None, notify_evicted, notify_closed_rx, watch_use)
946 .await;
947
948 assert!(
949 !evicted,
950 "sender drop without notify should not report eviction"
951 );
952 }
953
954 #[tokio::test]
955 async fn test_idle_timeout_reports_notify_closed_not_evicted() {
956 let meta = ConnectionMeta::new(101, 1);
957 let cp: ConnectionPool<String> = ConnectionPool::new(1);
958 let (notify_evicted, watch_use) = cp.put(&meta, "v1".to_string());
959 let (notify_closed, notify_closed_rx) = watch::channel(false);
960
961 notify_closed.send(true).unwrap();
962
963 let evicted = cp
964 .idle_timeout(&meta, None, notify_evicted, notify_closed_rx, watch_use)
965 .await;
966
967 assert!(!evicted, "notify_closed should not report eviction");
968 assert!(cp.get(&meta.key).is_none());
969 }
970
971 #[test]
972 fn test_pool_node_is_empty() {
973 let node: PoolNode<String> = PoolNode::new();
974 assert!(node.is_empty(), "newly created node should be empty");
975
976 node.insert(1, "v1".to_string());
977 assert!(!node.is_empty(), "node with one item should not be empty");
978
979 let item = node.get_any();
981 assert!(item.is_some());
982 assert!(node.is_empty(), "node should be empty after get_any");
983
984 node.insert(2, "v2".to_string());
986 assert!(!node.is_empty());
987
988 let removed = node.remove(2);
989 assert!(removed.is_some());
990 assert!(node.is_empty(), "node should be empty after remove");
991 }
992
993 #[test]
994 fn test_pool_node_is_empty_overflow_to_connections() {
995 let node: PoolNode<String> = PoolNode::new();
998
999 for i in 0..(HOT_QUEUE_SIZE as i32 + 4) {
1000 node.insert(i, format!("v{i}"));
1001 }
1002 assert!(!node.is_empty());
1003
1004 while node.get_any().is_some() {}
1006 assert!(node.is_empty(), "node should be empty after draining all");
1007 }
1008
1009 #[tokio::test]
1010 async fn test_empty_node_removed_after_pop_closed() {
1011 let meta = ConnectionMeta::new(101, 1);
1014 let cp: ConnectionPool<String> = ConnectionPool::new(2);
1015 cp.put(&meta, "v1".to_string());
1016
1017 assert_eq!(pool_len(&cp), 1, "pool should have 1 node");
1018
1019 cp.pop_closed(&meta);
1020
1021 assert_eq!(
1022 pool_len(&cp),
1023 0,
1024 "empty PoolNode should be removed after pop_closed"
1025 );
1026 }
1027
1028 #[tokio::test]
1029 async fn test_empty_node_removed_after_get() {
1030 let meta = ConnectionMeta::new(101, 1);
1035 let cp: ConnectionPool<String> = ConnectionPool::new(2);
1036 cp.put(&meta, "v1".to_string());
1037
1038 assert_eq!(pool_len(&cp), 1);
1039
1040 let conn = cp.get(&meta.key);
1041 assert!(conn.is_some());
1042
1043 assert_eq!(
1044 pool_len(&cp),
1045 0,
1046 "empty PoolNode should be removed after get() takes the last connection"
1047 );
1048 }
1049
1050 #[tokio::test]
1051 async fn test_empty_node_removed_when_get_finds_empty_node() {
1052 let meta1 = ConnectionMeta::new(101, 1);
1055 let meta2 = ConnectionMeta::new(101, 2);
1056 let cp: ConnectionPool<String> = ConnectionPool::new(4);
1057 cp.put(&meta1, "v1".to_string());
1058 cp.put(&meta2, "v2".to_string());
1059
1060 cp.pop_closed(&meta1);
1063 assert_eq!(pool_len(&cp), 1, "node should still exist");
1064
1065 cp.pop_closed(&meta2);
1066 assert_eq!(
1067 pool_len(&cp),
1068 0,
1069 "node should be removed after last connection is popped"
1070 );
1071 }
1072
1073 #[tokio::test]
1074 async fn test_node_not_removed_when_connections_remain() {
1075 let meta1 = ConnectionMeta::new(101, 1);
1078 let meta2 = ConnectionMeta::new(101, 2);
1079 let cp: ConnectionPool<String> = ConnectionPool::new(4);
1080 cp.put(&meta1, "v1".to_string());
1081 cp.put(&meta2, "v2".to_string());
1082
1083 cp.pop_closed(&meta1);
1084
1085 assert!(
1086 pool_contains(&cp, 101),
1087 "node should still exist because meta2's connection is still in it"
1088 );
1089 assert_eq!(pool_len(&cp), 1);
1090
1091 let conn = cp.get(&meta1.key);
1093 assert!(conn.is_some());
1094 }
1095
1096 #[tokio::test]
1097 async fn test_empty_node_cleanup_only_affects_target_key() {
1098 let meta_a = ConnectionMeta::new(101, 1);
1100 let meta_b = ConnectionMeta::new(202, 2);
1101 let cp: ConnectionPool<String> = ConnectionPool::new(4);
1102 cp.put(&meta_a, "a".to_string());
1103 cp.put(&meta_b, "b".to_string());
1104
1105 assert_eq!(pool_len(&cp), 2);
1106
1107 cp.pop_closed(&meta_a);
1109
1110 assert_eq!(
1111 pool_len(&cp),
1112 1,
1113 "only key 101's empty node should be removed"
1114 );
1115 assert!(!pool_contains(&cp, 101), "key 101 should be gone");
1116 assert!(pool_contains(&cp, 202), "key 202 should remain");
1117
1118 let conn = cp.get(&meta_b.key);
1120 assert_eq!(conn, Some("b".to_string()));
1121 }
1122
1123 #[tokio::test]
1124 async fn test_empty_node_cleaned_after_lru_eviction() {
1125 let meta1 = ConnectionMeta::new(101, 1);
1128 let meta2 = ConnectionMeta::new(202, 2);
1129 let cp: ConnectionPool<String> = ConnectionPool::new(1);
1130
1131 cp.put(&meta1, "v1".to_string());
1132 assert_eq!(pool_len(&cp), 1);
1133
1134 cp.put(&meta2, "v2".to_string());
1136
1137 assert!(
1138 !pool_contains(&cp, 101),
1139 "key 101's empty node should be removed after its only connection was evicted"
1140 );
1141 assert!(pool_contains(&cp, 202));
1142 }
1143
1144 #[test]
1145 fn test_concurrent_empty_node_cleanup_does_not_orphan_put() {
1146 const KEY: GroupKey = 101;
1147 let cp = Arc::new(ConnectionPool::new(2_000));
1148 let start = Arc::new(std::sync::Barrier::new(2));
1149
1150 let cleanup_cp = cp.clone();
1151 let cleanup_start = start.clone();
1152 let cleanup = std::thread::spawn(move || {
1153 cleanup_start.wait();
1154 for _ in 0..10_000 {
1155 cleanup_cp.try_remove_empty_node(KEY);
1156 std::thread::yield_now();
1157 }
1158 });
1159
1160 start.wait();
1161 for id in 1..=1_000 {
1162 let value = format!("v{id}");
1163 cp.put(&ConnectionMeta::new(KEY, id), value.clone());
1164 assert_eq!(
1165 cp.get(&KEY),
1166 Some(value),
1167 "put connection should remain reachable during empty-node cleanup"
1168 );
1169 std::thread::yield_now();
1170 }
1171
1172 cleanup.join().unwrap();
1173 }
1174
1175 #[tokio::test]
1176 async fn test_node_reusable_after_cleanup() {
1177 let meta1 = ConnectionMeta::new(101, 1);
1180 let cp: ConnectionPool<String> = ConnectionPool::new(4);
1181 cp.put(&meta1, "first".to_string());
1182
1183 cp.pop_closed(&meta1);
1184 assert_eq!(pool_len(&cp), 0, "node should be cleaned up");
1185
1186 let meta2 = ConnectionMeta::new(101, 2);
1188 cp.put(&meta2, "second".to_string());
1189
1190 assert_eq!(pool_len(&cp), 1);
1191 let conn = cp.get(&meta2.key);
1192 assert_eq!(conn, Some("second".to_string()));
1193
1194 assert_eq!(
1195 pool_len(&cp),
1196 0,
1197 "node should be cleaned up again after get"
1198 );
1199 }
1200}