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