Skip to main content

solana_connection_cache/
connection_cache.rs

1use {
2    crate::{
3        client_connection::ClientConnection as BlockingClientConnection,
4        connection_cache_stats::{CONNECTION_STAT_SUBMISSION_INTERVAL, ConnectionCacheStats},
5        nonblocking::client_connection::ClientConnection as NonblockingClientConnection,
6    },
7    crossbeam_channel::{Receiver, RecvError, Sender},
8    indexmap::map::IndexMap,
9    log::*,
10    rand::{Rng, rng},
11    solana_keypair::Keypair,
12    solana_measure::measure::Measure,
13    solana_time_utils::AtomicInterval,
14    std::{
15        net::SocketAddr,
16        sync::{Arc, RwLock, atomic::Ordering},
17        thread::{Builder, JoinHandle},
18    },
19    thiserror::Error,
20};
21
22/// Default maximum number of connections to keep
23pub const DEFAULT_MAX_CONNECTIONS: usize = 1024;
24
25/// Default connection pool size per remote address
26pub const DEFAULT_CONNECTION_POOL_SIZE: usize = 2;
27
28#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
29pub enum Protocol {
30    UDP,
31    QUIC,
32}
33
34pub trait ConnectionManager: Send + Sync + 'static {
35    type ConnectionPool: ConnectionPool;
36    type NewConnectionConfig: NewConnectionConfig;
37
38    const PROTOCOL: Protocol;
39
40    fn new_connection_pool(&self) -> Self::ConnectionPool;
41    fn new_connection_config(&self) -> Self::NewConnectionConfig;
42    fn update_key(&self, _key: &Keypair) -> Result<(), Box<dyn std::error::Error>>;
43}
44
45pub struct ConnectionCache<
46    R, // ConnectionPool
47    S, // ConnectionManager
48    T, // NewConnectionConfig
49> {
50    name: &'static str,
51    map: Arc<RwLock<IndexMap<SocketAddr, /*ConnectionPool:*/ R>>>,
52    connection_manager: Arc<S>,
53    stats: Arc<ConnectionCacheStats>,
54    last_stats: AtomicInterval,
55    connection_pool_size: usize,
56    max_connections: usize,
57    connection_config: Arc<T>,
58    sender: Sender<(usize, SocketAddr)>,
59}
60
61impl<P, M, C> ConnectionCache<P, M, C>
62where
63    P: ConnectionPool<NewConnectionConfig = C>,
64    M: ConnectionManager<ConnectionPool = P, NewConnectionConfig = C>,
65    C: NewConnectionConfig,
66{
67    pub fn new(
68        name: &'static str,
69        connection_manager: M,
70        connection_pool_size: usize,
71    ) -> Result<Self, ClientError> {
72        Self::new_with_max_connections(
73            name,
74            connection_manager,
75            connection_pool_size,
76            DEFAULT_MAX_CONNECTIONS,
77        )
78    }
79
80    pub fn new_with_max_connections(
81        name: &'static str,
82        connection_manager: M,
83        connection_pool_size: usize,
84        max_connections: usize,
85    ) -> Result<Self, ClientError> {
86        let config = Arc::new(connection_manager.new_connection_config());
87        let max_connections = 1.max(max_connections); // The minimum is 1.
88        info!(
89            "Creating ConnectionCache {name}, pool size: {connection_pool_size}, max connections: \
90             {max_connections}"
91        );
92        let (sender, receiver) = crossbeam_channel::unbounded();
93
94        let map = Arc::new(RwLock::new(IndexMap::with_capacity(max_connections)));
95        let connection_manager = Arc::new(connection_manager);
96        let connection_pool_size = 1.max(connection_pool_size); // The minimum pool size is 1.
97
98        let stats = Arc::new(ConnectionCacheStats::default());
99
100        let _async_connection_thread =
101            Self::create_connection_async_thread(map.clone(), receiver, stats.clone());
102        Ok(Self {
103            name,
104            map,
105            stats,
106            connection_manager,
107            last_stats: AtomicInterval::default(),
108            connection_pool_size,
109            max_connections,
110            connection_config: config,
111            sender,
112        })
113    }
114
115    /// This actually triggers the connection creation by sending empty data
116    fn create_connection_async_thread(
117        map: Arc<RwLock<IndexMap<SocketAddr, /*ConnectionPool:*/ P>>>,
118        receiver: Receiver<(usize, SocketAddr)>,
119        stats: Arc<ConnectionCacheStats>,
120    ) -> JoinHandle<()> {
121        Builder::new()
122            .name("solQAsynCon".to_string())
123            .spawn(move || {
124                loop {
125                    let recv_result = receiver.recv();
126                    match recv_result {
127                        Err(RecvError) => {
128                            break;
129                        }
130                        Ok((idx, addr)) => {
131                            let map = map.read().unwrap();
132                            let pool = map.get(&addr);
133                            if let Some(pool) = pool {
134                                let conn = pool.get(idx);
135                                if let Ok(conn) = conn {
136                                    drop(map);
137                                    let conn = conn.new_blocking_connection(addr, stats.clone());
138                                    let result = conn.send_data(&[]);
139                                    debug!("Create async connection result {result:?} for {addr}");
140                                }
141                            }
142                        }
143                    }
144                }
145            })
146            .unwrap()
147    }
148
149    pub fn update_key(&self, key: &Keypair) -> Result<(), Box<dyn std::error::Error>> {
150        let mut map = self.map.write().unwrap();
151        map.clear();
152        self.connection_manager.update_key(key)
153    }
154    /// Create a lazy connection object under the exclusive lock of the cache map if there is not
155    /// enough used connections in the connection pool for the specified address.
156    /// Returns CreateConnectionResult.
157    fn create_connection(
158        &self,
159        lock_timing_ms: &mut u64,
160        addr: &SocketAddr,
161    ) -> CreateConnectionResult<<P as ConnectionPool>::BaseClientConnection> {
162        let mut get_connection_map_lock_measure = Measure::start("get_connection_map_lock_measure");
163        let mut map = self.map.write().unwrap();
164        get_connection_map_lock_measure.stop();
165        *lock_timing_ms = lock_timing_ms.saturating_add(get_connection_map_lock_measure.as_ms());
166        // Read again, as it is possible that between read lock dropped and the write lock acquired
167        // another thread could have setup the connection.
168
169        let pool_status = map
170            .get(addr)
171            .map(|pool| pool.check_pool_status(self.connection_pool_size))
172            .unwrap_or(PoolStatus::Empty);
173
174        let (cache_hit, num_evictions, eviction_timing_ms) =
175            if matches!(pool_status, PoolStatus::Empty) {
176                Self::create_connection_internal(
177                    &self.connection_config,
178                    &self.connection_manager,
179                    &mut map,
180                    addr,
181                    self.connection_pool_size,
182                    self.max_connections,
183                    None,
184                )
185            } else {
186                (true, 0, 0)
187            };
188
189        if matches!(pool_status, PoolStatus::PartiallyFull) {
190            // trigger an async connection create
191            debug!("Triggering async connection for {addr:?}");
192            Self::create_connection_internal(
193                &self.connection_config,
194                &self.connection_manager,
195                &mut map,
196                addr,
197                self.connection_pool_size,
198                self.max_connections,
199                Some(&self.sender),
200            );
201        }
202
203        let pool = map.get(addr).unwrap();
204        let connection = pool.borrow_connection();
205
206        CreateConnectionResult {
207            connection,
208            cache_hit,
209            connection_cache_stats: self.stats.clone(),
210            num_evictions,
211            eviction_timing_ms,
212        }
213    }
214
215    fn create_connection_internal(
216        config: &C,
217        connection_manager: &M,
218        map: &mut std::sync::RwLockWriteGuard<'_, IndexMap<SocketAddr, P>>,
219        addr: &SocketAddr,
220        connection_pool_size: usize,
221        max_connections: usize,
222        async_connection_sender: Option<&Sender<(usize, SocketAddr)>>,
223    ) -> (bool, u64, u64) {
224        // evict a connection if the cache is reaching upper bounds
225        let mut num_evictions = 0;
226        let mut get_connection_cache_eviction_measure =
227            Measure::start("get_connection_cache_eviction_measure");
228        let existing_index = map.get_index_of(addr);
229        while map.len() >= max_connections {
230            let mut rng = rng();
231            let n = rng.random_range(0..max_connections);
232            if let Some(index) = existing_index
233                && n == index
234            {
235                continue;
236            }
237            map.swap_remove_index(n);
238            num_evictions += 1;
239        }
240        get_connection_cache_eviction_measure.stop();
241
242        let mut hit_cache = false;
243        map.entry(*addr)
244            .and_modify(|pool| {
245                if matches!(
246                    pool.check_pool_status(connection_pool_size),
247                    PoolStatus::PartiallyFull
248                ) {
249                    let idx = pool.add_connection(config, addr);
250                    if let Some(sender) = async_connection_sender {
251                        debug!(
252                            "Sending async connection creation {} for {addr}",
253                            pool.num_connections() - 1
254                        );
255                        sender.send((idx, *addr)).unwrap();
256                    };
257                } else {
258                    hit_cache = true;
259                }
260            })
261            .or_insert_with(|| {
262                let mut pool = connection_manager.new_connection_pool();
263                pool.add_connection(config, addr);
264                pool
265            });
266        (
267            hit_cache,
268            num_evictions,
269            get_connection_cache_eviction_measure.as_ms(),
270        )
271    }
272
273    fn get_or_add_connection(
274        &self,
275        addr: &SocketAddr,
276    ) -> GetConnectionResult<<P as ConnectionPool>::BaseClientConnection> {
277        let mut get_connection_map_lock_measure = Measure::start("get_connection_map_lock_measure");
278        let map = self.map.read().unwrap();
279        get_connection_map_lock_measure.stop();
280
281        let mut lock_timing_ms = get_connection_map_lock_measure.as_ms();
282
283        let report_stats = self
284            .last_stats
285            .should_update(CONNECTION_STAT_SUBMISSION_INTERVAL);
286
287        let mut get_connection_map_measure = Measure::start("get_connection_hit_measure");
288        let CreateConnectionResult {
289            connection,
290            cache_hit,
291            connection_cache_stats,
292            num_evictions,
293            eviction_timing_ms,
294        } = match map.get(addr) {
295            Some(pool) => {
296                let pool_status = pool.check_pool_status(self.connection_pool_size);
297                match pool_status {
298                    PoolStatus::Empty => {
299                        // create more connection and put it in the pool
300                        drop(map);
301                        self.create_connection(&mut lock_timing_ms, addr)
302                    }
303                    PoolStatus::PartiallyFull | PoolStatus::Full => {
304                        let connection = pool.borrow_connection();
305                        if matches!(pool_status, PoolStatus::PartiallyFull) {
306                            debug!("Creating connection async for {addr}");
307                            drop(map);
308                            let mut map = self.map.write().unwrap();
309                            Self::create_connection_internal(
310                                &self.connection_config,
311                                &self.connection_manager,
312                                &mut map,
313                                addr,
314                                self.connection_pool_size,
315                                self.max_connections,
316                                Some(&self.sender),
317                            );
318                        }
319                        CreateConnectionResult {
320                            connection,
321                            cache_hit: true,
322                            connection_cache_stats: self.stats.clone(),
323                            num_evictions: 0,
324                            eviction_timing_ms: 0,
325                        }
326                    }
327                }
328            }
329            None => {
330                // Upgrade to write access by dropping read lock and acquire write lock
331                drop(map);
332                self.create_connection(&mut lock_timing_ms, addr)
333            }
334        };
335        get_connection_map_measure.stop();
336
337        GetConnectionResult {
338            connection,
339            cache_hit,
340            report_stats,
341            map_timing_ms: get_connection_map_measure.as_ms(),
342            lock_timing_ms,
343            connection_cache_stats,
344            num_evictions,
345            eviction_timing_ms,
346        }
347    }
348
349    fn get_connection_and_log_stats(
350        &self,
351        addr: &SocketAddr,
352    ) -> (
353        Arc<<P as ConnectionPool>::BaseClientConnection>,
354        Arc<ConnectionCacheStats>,
355    ) {
356        let mut get_connection_measure = Measure::start("get_connection_measure");
357        let GetConnectionResult {
358            connection,
359            cache_hit,
360            report_stats,
361            map_timing_ms,
362            lock_timing_ms,
363            connection_cache_stats,
364            num_evictions,
365            eviction_timing_ms,
366        } = self.get_or_add_connection(addr);
367
368        if report_stats {
369            connection_cache_stats.report(self.name);
370        }
371
372        if cache_hit {
373            connection_cache_stats
374                .cache_hits
375                .fetch_add(1, Ordering::Relaxed);
376            connection_cache_stats
377                .get_connection_hit_ms
378                .fetch_add(map_timing_ms, Ordering::Relaxed);
379        } else {
380            connection_cache_stats
381                .cache_misses
382                .fetch_add(1, Ordering::Relaxed);
383            connection_cache_stats
384                .get_connection_miss_ms
385                .fetch_add(map_timing_ms, Ordering::Relaxed);
386            connection_cache_stats
387                .cache_evictions
388                .fetch_add(num_evictions, Ordering::Relaxed);
389            connection_cache_stats
390                .eviction_time_ms
391                .fetch_add(eviction_timing_ms, Ordering::Relaxed);
392        }
393
394        get_connection_measure.stop();
395        connection_cache_stats
396            .get_connection_lock_ms
397            .fetch_add(lock_timing_ms, Ordering::Relaxed);
398        connection_cache_stats
399            .get_connection_ms
400            .fetch_add(get_connection_measure.as_ms(), Ordering::Relaxed);
401
402        (connection, connection_cache_stats)
403    }
404
405    pub fn get_connection(&self, addr: &SocketAddr) -> Arc<<<P as ConnectionPool>::BaseClientConnection as BaseClientConnection>::BlockingClientConnection>{
406        let (connection, connection_cache_stats) = self.get_connection_and_log_stats(addr);
407        connection.new_blocking_connection(*addr, connection_cache_stats)
408    }
409
410    pub fn get_nonblocking_connection(
411        &self,
412        addr: &SocketAddr,
413    ) -> Arc<<<P as ConnectionPool>::BaseClientConnection as BaseClientConnection>::NonblockingClientConnection>{
414        let (connection, connection_cache_stats) = self.get_connection_and_log_stats(addr);
415        connection.new_nonblocking_connection(*addr, connection_cache_stats)
416    }
417}
418
419#[derive(Error, Debug)]
420pub enum ConnectionPoolError {
421    #[error("connection index is out of range of the pool")]
422    IndexOutOfRange,
423}
424
425#[derive(Error, Debug)]
426pub enum ClientError {
427    #[error("IO error: {0:?}")]
428    IoError(#[from] std::io::Error),
429}
430
431pub trait NewConnectionConfig: Sized + Send + Sync + 'static {
432    fn new() -> Result<Self, ClientError>;
433}
434
435pub enum PoolStatus {
436    Empty,
437    PartiallyFull,
438    Full,
439}
440
441pub trait ConnectionPool: Send + Sync + 'static {
442    type NewConnectionConfig: NewConnectionConfig;
443    type BaseClientConnection: BaseClientConnection;
444
445    /// Add a connection to the pool and return its index
446    fn add_connection(&mut self, config: &Self::NewConnectionConfig, addr: &SocketAddr) -> usize;
447
448    /// Get the number of current connections in the pool
449    fn num_connections(&self) -> usize;
450
451    /// Get a connection based on its index in the pool, without checking if the
452    fn get(&self, index: usize) -> Result<Arc<Self::BaseClientConnection>, ConnectionPoolError>;
453
454    /// Get a connection from the pool. It must have at least one connection in the pool.
455    /// This randomly picks a connection in the pool.
456    fn borrow_connection(&self) -> Arc<Self::BaseClientConnection> {
457        let mut rng = rng();
458        let n = rng.random_range(0..self.num_connections());
459        self.get(n).expect("index is within num_connections")
460    }
461
462    /// Check if we need to create a new connection. If the count of the connections
463    /// is smaller than the pool size and if there is no connection at all.
464    fn check_pool_status(&self, required_pool_size: usize) -> PoolStatus {
465        if self.num_connections() == 0 {
466            PoolStatus::Empty
467        } else if self.num_connections() < required_pool_size {
468            PoolStatus::PartiallyFull
469        } else {
470            PoolStatus::Full
471        }
472    }
473
474    fn create_pool_entry(
475        &self,
476        config: &Self::NewConnectionConfig,
477        addr: &SocketAddr,
478    ) -> Arc<Self::BaseClientConnection>;
479}
480
481pub trait BaseClientConnection {
482    type BlockingClientConnection: BlockingClientConnection;
483    type NonblockingClientConnection: NonblockingClientConnection;
484
485    fn new_blocking_connection(
486        &self,
487        addr: SocketAddr,
488        stats: Arc<ConnectionCacheStats>,
489    ) -> Arc<Self::BlockingClientConnection>;
490
491    fn new_nonblocking_connection(
492        &self,
493        addr: SocketAddr,
494        stats: Arc<ConnectionCacheStats>,
495    ) -> Arc<Self::NonblockingClientConnection>;
496}
497
498struct GetConnectionResult<T> {
499    connection: Arc</*BaseClientConnection:*/ T>,
500    cache_hit: bool,
501    report_stats: bool,
502    map_timing_ms: u64,
503    lock_timing_ms: u64,
504    connection_cache_stats: Arc<ConnectionCacheStats>,
505    num_evictions: u64,
506    eviction_timing_ms: u64,
507}
508
509struct CreateConnectionResult<T> {
510    connection: Arc</*BaseClientConnection:*/ T>,
511    cache_hit: bool,
512    connection_cache_stats: Arc<ConnectionCacheStats>,
513    num_evictions: u64,
514    eviction_timing_ms: u64,
515}
516
517#[cfg(test)]
518mod tests {
519    use {
520        super::*,
521        crate::{
522            client_connection::ClientConnection as BlockingClientConnection,
523            nonblocking::client_connection::ClientConnection as NonblockingClientConnection,
524        },
525        async_trait::async_trait,
526        rand::{Rng, SeedableRng},
527        rand_chacha::ChaChaRng,
528        solana_net_utils::sockets::bind_to_localhost_unique,
529        solana_transaction_error::TransportResult,
530        std::{
531            net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
532            sync::Arc,
533        },
534    };
535
536    struct MockUdpPool {
537        connections: Vec<Arc<MockUdp>>,
538    }
539    impl ConnectionPool for MockUdpPool {
540        type NewConnectionConfig = MockUdpConfig;
541        type BaseClientConnection = MockUdp;
542
543        /// Add a connection into the pool and return its index in the pool.
544        fn add_connection(
545            &mut self,
546            config: &Self::NewConnectionConfig,
547            addr: &SocketAddr,
548        ) -> usize {
549            let connection = self.create_pool_entry(config, addr);
550            let idx = self.connections.len();
551            self.connections.push(connection);
552            idx
553        }
554
555        fn num_connections(&self) -> usize {
556            self.connections.len()
557        }
558
559        fn get(
560            &self,
561            index: usize,
562        ) -> Result<Arc<Self::BaseClientConnection>, ConnectionPoolError> {
563            self.connections
564                .get(index)
565                .cloned()
566                .ok_or(ConnectionPoolError::IndexOutOfRange)
567        }
568
569        fn create_pool_entry(
570            &self,
571            config: &Self::NewConnectionConfig,
572            _addr: &SocketAddr,
573        ) -> Arc<Self::BaseClientConnection> {
574            Arc::new(MockUdp(config.udp_socket.clone()))
575        }
576    }
577
578    struct MockUdpConfig {
579        udp_socket: Arc<UdpSocket>,
580    }
581
582    impl Default for MockUdpConfig {
583        fn default() -> Self {
584            Self {
585                udp_socket: Arc::new(bind_to_localhost_unique().unwrap()),
586            }
587        }
588    }
589
590    impl NewConnectionConfig for MockUdpConfig {
591        fn new() -> Result<Self, ClientError> {
592            Ok(Self {
593                udp_socket: Arc::new(
594                    bind_to_localhost_unique().map_err(Into::<ClientError>::into)?,
595                ),
596            })
597        }
598    }
599
600    struct MockUdp(Arc<UdpSocket>);
601    impl BaseClientConnection for MockUdp {
602        type BlockingClientConnection = MockUdpConnection;
603        type NonblockingClientConnection = MockUdpConnection;
604
605        fn new_blocking_connection(
606            &self,
607            addr: SocketAddr,
608            _stats: Arc<ConnectionCacheStats>,
609        ) -> Arc<Self::BlockingClientConnection> {
610            Arc::new(MockUdpConnection {
611                _socket: self.0.clone(),
612                addr,
613            })
614        }
615
616        fn new_nonblocking_connection(
617            &self,
618            addr: SocketAddr,
619            _stats: Arc<ConnectionCacheStats>,
620        ) -> Arc<Self::NonblockingClientConnection> {
621            Arc::new(MockUdpConnection {
622                _socket: self.0.clone(),
623                addr,
624            })
625        }
626    }
627
628    struct MockUdpConnection {
629        _socket: Arc<UdpSocket>,
630        addr: SocketAddr,
631    }
632
633    #[derive(Default)]
634    struct MockConnectionManager {}
635
636    impl ConnectionManager for MockConnectionManager {
637        type ConnectionPool = MockUdpPool;
638        type NewConnectionConfig = MockUdpConfig;
639
640        const PROTOCOL: Protocol = Protocol::QUIC;
641
642        fn new_connection_pool(&self) -> Self::ConnectionPool {
643            MockUdpPool {
644                connections: Vec::default(),
645            }
646        }
647
648        fn new_connection_config(&self) -> Self::NewConnectionConfig {
649            MockUdpConfig::new().unwrap()
650        }
651
652        fn update_key(&self, _key: &Keypair) -> Result<(), Box<dyn std::error::Error>> {
653            Ok(())
654        }
655    }
656
657    impl BlockingClientConnection for MockUdpConnection {
658        fn server_addr(&self) -> &SocketAddr {
659            &self.addr
660        }
661        fn send_data(&self, _buffer: &[u8]) -> TransportResult<()> {
662            unimplemented!()
663        }
664        fn send_data_async(&self, _data: Arc<Vec<u8>>) -> TransportResult<()> {
665            unimplemented!()
666        }
667        fn send_data_batch(&self, _buffers: &[Vec<u8>]) -> TransportResult<()> {
668            unimplemented!()
669        }
670        fn send_data_batch_async(&self, _buffers: Vec<Vec<u8>>) -> TransportResult<()> {
671            unimplemented!()
672        }
673    }
674
675    #[async_trait]
676    impl NonblockingClientConnection for MockUdpConnection {
677        fn server_addr(&self) -> &SocketAddr {
678            &self.addr
679        }
680        async fn send_data(&self, _data: &[u8]) -> TransportResult<()> {
681            unimplemented!()
682        }
683        async fn send_data_batch(&self, _buffers: &[Vec<u8>]) -> TransportResult<()> {
684            unimplemented!()
685        }
686    }
687
688    fn get_addr(rng: &mut ChaChaRng) -> SocketAddr {
689        let a = rng.random_range(1..255);
690        let b = rng.random_range(1..255);
691        let c = rng.random_range(1..255);
692        let d = rng.random_range(1..255);
693
694        let addr_str = format!("{a}.{b}.{c}.{d}:80");
695
696        addr_str.parse().expect("Invalid address")
697    }
698
699    #[test]
700    fn test_connection_cache() {
701        agave_logger::setup();
702        // Allow the test to run deterministically
703        // with the same pseudorandom sequence between runs
704        // and on different platforms - the cryptographic security
705        // property isn't important here but ChaChaRng provides a way
706        // to get the same pseudorandom sequence on different platforms
707        let mut rng = ChaChaRng::seed_from_u64(42);
708
709        // Generate a bunch of random addresses and create connections to them
710        // Since ClientConnection::new is infallible, it should't matter whether or not
711        // we can actually connect to those addresses - ClientConnection implementations should either
712        // be lazy and not connect until first use or handle connection errors somehow
713        // (without crashing, as would be required in a real practical validator)
714        let connection_manager = MockConnectionManager::default();
715        let connection_cache = ConnectionCache::new(
716            "connection_cache_test",
717            connection_manager,
718            DEFAULT_CONNECTION_POOL_SIZE,
719        )
720        .unwrap();
721        let addrs = (0..DEFAULT_MAX_CONNECTIONS)
722            .map(|_| {
723                let addr = get_addr(&mut rng);
724                connection_cache.get_connection(&addr);
725                addr
726            })
727            .collect::<Vec<_>>();
728        {
729            let map = connection_cache.map.read().unwrap();
730            assert!(map.len() == DEFAULT_MAX_CONNECTIONS);
731            addrs.iter().for_each(|addr| {
732                let conn = &map.get(addr).expect("Address not found").get(0).unwrap();
733                let conn = conn.new_blocking_connection(*addr, connection_cache.stats.clone());
734                assert_eq!(
735                    BlockingClientConnection::server_addr(&*conn).ip(),
736                    addr.ip(),
737                );
738                assert_eq!(
739                    NonblockingClientConnection::server_addr(&*conn).ip(),
740                    addr.ip(),
741                );
742            });
743        }
744
745        let addr = &get_addr(&mut rng);
746        connection_cache.get_connection(addr);
747
748        let port = addr.port();
749        let addr_with_quic_port = SocketAddr::new(addr.ip(), port);
750        let map = connection_cache.map.read().unwrap();
751        assert!(map.len() == DEFAULT_MAX_CONNECTIONS);
752        let _conn = map.get(&addr_with_quic_port).expect("Address not found");
753    }
754
755    // Test that we can get_connection with a connection cache configured
756    // on an address with a port that would overflow to
757    // an invalid port.
758    #[test]
759    fn test_overflow_address() {
760        let port = u16::MAX;
761        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
762        let connection_manager = MockConnectionManager::default();
763        let connection_cache =
764            ConnectionCache::new("connection_cache_test", connection_manager, 1).unwrap();
765
766        let conn = connection_cache.get_connection(&addr);
767        // We (intentionally) don't have an interface that allows us to distinguish between
768        // UDP and Quic connections, so check instead that the port is valid (non-zero)
769        // and is the same as the input port (falling back on UDP)
770        assert_ne!(port, 0u16);
771        assert_eq!(BlockingClientConnection::server_addr(&*conn).port(), port);
772        assert_eq!(
773            NonblockingClientConnection::server_addr(&*conn).port(),
774            port
775        );
776    }
777}