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
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, S, T, > {
50 name: &'static str,
51 map: Arc<RwLock<IndexMap<SocketAddr, 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); 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); 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 fn create_connection_async_thread(
117 map: Arc<RwLock<IndexMap<SocketAddr, 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 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 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 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 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 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 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 fn add_connection(&mut self, config: &Self::NewConnectionConfig, addr: &SocketAddr) -> usize;
447
448 fn num_connections(&self) -> usize;
450
451 fn get(&self, index: usize) -> Result<Arc<Self::BaseClientConnection>, ConnectionPoolError>;
453
454 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 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<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<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 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 let mut rng = ChaChaRng::seed_from_u64(42);
708
709 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]
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 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}