1
- use std:: time:: Duration ;
1
+ use std:: { cmp :: min , time:: Duration } ;
2
2
3
3
use async_trait:: async_trait;
4
4
use bb8:: { Pool , PooledConnection } ;
5
5
use light_client:: rpc:: { rpc_connection:: RpcConnectionConfig , RpcConnection , RpcError } ;
6
6
use solana_sdk:: commitment_config:: CommitmentConfig ;
7
7
use thiserror:: Error ;
8
8
use tokio:: time:: sleep;
9
- use tracing:: { debug , error } ;
9
+ use tracing:: { error , trace , warn } ;
10
10
11
11
use crate :: rate_limiter:: RateLimiter ;
12
12
@@ -18,6 +18,10 @@ pub enum PoolError {
18
18
RpcRequest ( #[ from] RpcError ) ,
19
19
#[ error( "Pool error: {0}" ) ]
20
20
Pool ( String ) ,
21
+ #[ error( "Failed to get connection after {0} retries: {1}" ) ]
22
+ MaxRetriesExceeded ( u32 , String ) ,
23
+ #[ error( "Missing required field for RpcPoolBuilder: {0}" ) ]
24
+ BuilderMissingField ( String ) ,
21
25
}
22
26
23
27
pub struct SolanaConnectionManager < R : RpcConnection + ' static > {
@@ -57,6 +61,7 @@ impl<R: RpcConnection + 'static> bb8::ManageConnection for SolanaConnectionManag
57
61
commitment_config : Some ( self . commitment ) ,
58
62
with_indexer : false ,
59
63
} ;
64
+
60
65
Ok ( R :: new ( config) )
61
66
}
62
67
@@ -72,67 +77,182 @@ impl<R: RpcConnection + 'static> bb8::ManageConnection for SolanaConnectionManag
72
77
#[ derive( Debug ) ]
73
78
pub struct SolanaRpcPool < R : RpcConnection + ' static > {
74
79
pool : Pool < SolanaConnectionManager < R > > ,
80
+ max_retries : u32 ,
81
+ initial_retry_delay : Duration ,
82
+ max_retry_delay : Duration ,
75
83
}
76
84
77
- impl < R : RpcConnection + ' static > SolanaRpcPool < R > {
78
- pub async fn new (
79
- url : String ,
80
- commitment : CommitmentConfig ,
81
- max_size : u32 ,
82
- rpc_rate_limiter : Option < RateLimiter > ,
83
- send_tx_rate_limiter : Option < RateLimiter > ,
84
- ) -> Result < Self , PoolError > {
85
- let manager =
86
- SolanaConnectionManager :: new ( url, commitment, rpc_rate_limiter, send_tx_rate_limiter) ;
85
+ #[ derive( Debug ) ]
86
+ pub struct SolanaRpcPoolBuilder < R : RpcConnection > {
87
+ url : Option < String > ,
88
+ commitment : Option < CommitmentConfig > ,
89
+
90
+ max_size : u32 ,
91
+ connection_timeout_secs : u64 ,
92
+ idle_timeout_secs : u64 ,
93
+ max_retries : u32 ,
94
+ initial_retry_delay_ms : u64 ,
95
+ max_retry_delay_ms : u64 ,
96
+
97
+ rpc_rate_limiter : Option < RateLimiter > ,
98
+ send_tx_rate_limiter : Option < RateLimiter > ,
99
+ _phantom : std:: marker:: PhantomData < R > ,
100
+ }
101
+
102
+ impl < R : RpcConnection > Default for SolanaRpcPoolBuilder < R > {
103
+ fn default ( ) -> Self {
104
+ Self :: new ( )
105
+ }
106
+ }
107
+
108
+ impl < R : RpcConnection > SolanaRpcPoolBuilder < R > {
109
+ pub fn new ( ) -> Self {
110
+ Self {
111
+ url : None ,
112
+ commitment : None ,
113
+ max_size : 50 ,
114
+ connection_timeout_secs : 15 ,
115
+ idle_timeout_secs : 300 ,
116
+ max_retries : 3 ,
117
+ initial_retry_delay_ms : 1000 ,
118
+ max_retry_delay_ms : 16000 ,
119
+ rpc_rate_limiter : None ,
120
+ send_tx_rate_limiter : None ,
121
+ _phantom : std:: marker:: PhantomData ,
122
+ }
123
+ }
124
+
125
+ pub fn url ( mut self , url : String ) -> Self {
126
+ self . url = Some ( url) ;
127
+ self
128
+ }
129
+
130
+ pub fn commitment ( mut self , commitment : CommitmentConfig ) -> Self {
131
+ self . commitment = Some ( commitment) ;
132
+ self
133
+ }
134
+
135
+ pub fn max_size ( mut self , max_size : u32 ) -> Self {
136
+ self . max_size = max_size;
137
+ self
138
+ }
139
+
140
+ pub fn connection_timeout_secs ( mut self , secs : u64 ) -> Self {
141
+ self . connection_timeout_secs = secs;
142
+ self
143
+ }
144
+
145
+ pub fn idle_timeout_secs ( mut self , secs : u64 ) -> Self {
146
+ self . idle_timeout_secs = secs;
147
+ self
148
+ }
149
+
150
+ pub fn max_retries ( mut self , retries : u32 ) -> Self {
151
+ self . max_retries = retries;
152
+ self
153
+ }
154
+
155
+ pub fn initial_retry_delay_ms ( mut self , ms : u64 ) -> Self {
156
+ self . initial_retry_delay_ms = ms;
157
+ self
158
+ }
159
+
160
+ pub fn max_retry_delay_ms ( mut self , ms : u64 ) -> Self {
161
+ self . max_retry_delay_ms = ms;
162
+ self
163
+ }
164
+
165
+ pub fn rpc_rate_limiter ( mut self , limiter : RateLimiter ) -> Self {
166
+ self . rpc_rate_limiter = Some ( limiter) ;
167
+ self
168
+ }
169
+
170
+ pub fn send_tx_rate_limiter ( mut self , limiter : RateLimiter ) -> Self {
171
+ self . send_tx_rate_limiter = Some ( limiter) ;
172
+ self
173
+ }
174
+
175
+ pub async fn build ( self ) -> Result < SolanaRpcPool < R > , PoolError > {
176
+ let url = self
177
+ . url
178
+ . ok_or_else ( || PoolError :: BuilderMissingField ( "url" . to_string ( ) ) ) ?;
179
+ let commitment = self
180
+ . commitment
181
+ . ok_or_else ( || PoolError :: BuilderMissingField ( "commitment" . to_string ( ) ) ) ?;
182
+
183
+ let manager = SolanaConnectionManager :: new (
184
+ url,
185
+ commitment,
186
+ self . rpc_rate_limiter ,
187
+ self . send_tx_rate_limiter ,
188
+ ) ;
189
+
87
190
let pool = Pool :: builder ( )
88
- . max_size ( max_size)
89
- . connection_timeout ( Duration :: from_secs ( 15 ) )
90
- . idle_timeout ( Some ( Duration :: from_secs ( 60 * 5 ) ) )
191
+ . max_size ( self . max_size )
192
+ . connection_timeout ( Duration :: from_secs ( self . connection_timeout_secs ) )
193
+ . idle_timeout ( Some ( Duration :: from_secs ( self . idle_timeout_secs ) ) )
91
194
. build ( manager)
92
195
. await
93
196
. map_err ( |e| PoolError :: Pool ( e. to_string ( ) ) ) ?;
94
197
95
- Ok ( Self { pool } )
198
+ Ok ( SolanaRpcPool {
199
+ pool,
200
+ max_retries : self . max_retries ,
201
+ initial_retry_delay : Duration :: from_millis ( self . initial_retry_delay_ms ) ,
202
+ max_retry_delay : Duration :: from_millis ( self . max_retry_delay_ms ) ,
203
+ } )
96
204
}
205
+ }
97
206
207
+ impl < R : RpcConnection > SolanaRpcPool < R > {
98
208
pub async fn get_connection (
99
209
& self ,
100
210
) -> Result < PooledConnection < ' _ , SolanaConnectionManager < R > > , PoolError > {
101
- debug ! ( "Attempting to get RPC connection..." ) ;
102
- let result = self
103
- . pool
104
- . get ( )
105
- . await
106
- . map_err ( |e| PoolError :: Pool ( e. to_string ( ) ) ) ;
211
+ let mut current_retries = 0 ;
212
+ let mut current_delay = self . initial_retry_delay ;
107
213
108
- match result {
109
- Ok ( _) => {
110
- debug ! ( "Successfully got RPC connection" ) ;
111
- }
112
- Err ( ref e) => {
113
- error ! ( "Failed to get RPC connection: {:?}" , e) ;
114
- }
115
- }
116
-
117
- result
118
- }
119
-
120
- pub async fn get_connection_with_retry (
121
- & self ,
122
- max_retries : u32 ,
123
- delay : Duration ,
124
- ) -> Result < PooledConnection < ' _ , SolanaConnectionManager < R > > , PoolError > {
125
- let mut retries = 0 ;
126
214
loop {
215
+ trace ! (
216
+ "Attempting to get RPC connection... (Attempt {})" ,
217
+ current_retries + 1
218
+ ) ;
127
219
match self . pool . get ( ) . await {
128
- Ok ( conn) => return Ok ( conn) ,
129
- Err ( e) if retries < max_retries => {
130
- retries += 1 ;
131
- eprintln ! ( "Failed to get connection (attempt {}): {:?}" , retries, e) ;
132
- tokio:: task:: yield_now ( ) . await ;
133
- sleep ( delay) . await ;
220
+ Ok ( conn) => {
221
+ trace ! (
222
+ "Successfully got RPC connection (Attempt {})" ,
223
+ current_retries + 1
224
+ ) ;
225
+ return Ok ( conn) ;
226
+ }
227
+ Err ( e) => {
228
+ error ! (
229
+ "Failed to get RPC connection (Attempt {}): {:?}" ,
230
+ current_retries + 1 ,
231
+ e
232
+ ) ;
233
+ if current_retries < self . max_retries {
234
+ current_retries += 1 ;
235
+ warn ! (
236
+ "Retrying to get RPC connection in {:?} (Attempt {}/{})" ,
237
+ current_delay,
238
+ current_retries + 1 ,
239
+ self . max_retries + 1
240
+ ) ;
241
+ tokio:: task:: yield_now ( ) . await ;
242
+ sleep ( current_delay) . await ;
243
+ current_delay = min ( current_delay * 2 , self . max_retry_delay ) ;
244
+ } else {
245
+ error ! (
246
+ "Failed to get RPC connection after {} attempts. Last error: {:?}" ,
247
+ self . max_retries + 1 ,
248
+ e
249
+ ) ;
250
+ return Err ( PoolError :: MaxRetriesExceeded (
251
+ self . max_retries + 1 ,
252
+ e. to_string ( ) ,
253
+ ) ) ;
254
+ }
134
255
}
135
- Err ( e) => return Err ( PoolError :: Pool ( e. to_string ( ) ) ) ,
136
256
}
137
257
}
138
258
}
0 commit comments