@@ -6,12 +6,12 @@ use std::{
6
6
sync:: { Arc , RwLock } ,
7
7
} ;
8
8
9
- use futures_util:: StreamExt ;
9
+ use futures_util:: { stream :: FuturesUnordered , Stream , StreamExt } ;
10
10
use log:: * ;
11
11
use magicblock_metrics:: metrics;
12
- use solana_account_decoder:: { UiAccountEncoding , UiDataSliceConfig } ;
12
+ use solana_account_decoder:: { UiAccount , UiAccountEncoding , UiDataSliceConfig } ;
13
13
use solana_pubsub_client:: nonblocking:: pubsub_client:: PubsubClient ;
14
- use solana_rpc_client_api:: config:: RpcAccountInfoConfig ;
14
+ use solana_rpc_client_api:: { config:: RpcAccountInfoConfig , response :: Response } ;
15
15
use solana_sdk:: {
16
16
clock:: { Clock , Slot } ,
17
17
commitment_config:: { CommitmentConfig , CommitmentLevel } ,
@@ -23,6 +23,13 @@ use tokio::sync::mpsc::Receiver;
23
23
use tokio_stream:: StreamMap ;
24
24
use tokio_util:: sync:: CancellationToken ;
25
25
26
+ type BoxFn = Box <
27
+ dyn FnOnce ( ) -> Pin < Box < dyn Future < Output = ( ) > + Send + ' static > > + Send ,
28
+ > ;
29
+
30
+ type SubscriptionStream =
31
+ Pin < Box < dyn Stream < Item = Response < UiAccount > > + Send + ' static > > ;
32
+
26
33
#[ derive( Debug , Error ) ]
27
34
pub enum RemoteAccountUpdatesShardError {
28
35
#[ error( transparent) ]
@@ -66,11 +73,9 @@ impl RemoteAccountUpdatesShard {
66
73
) -> Result < ( ) , RemoteAccountUpdatesShardError > {
67
74
// Create a pubsub client
68
75
info ! ( "Shard {}: Starting" , self . shard_id) ;
69
- let pubsub_client = PubsubClient :: new ( & self . url )
70
- . await
71
- . map_err ( RemoteAccountUpdatesShardError :: PubsubClientError ) ?;
76
+ let ws_url = self . url . as_str ( ) ;
72
77
// For every account, we only want the updates, not the actual content of the accounts
73
- let rpc_account_info_config = Some ( RpcAccountInfoConfig {
78
+ let config = RpcAccountInfoConfig {
74
79
commitment : self
75
80
. commitment
76
81
. map ( |commitment| CommitmentConfig { commitment } ) ,
@@ -80,21 +85,13 @@ impl RemoteAccountUpdatesShard {
80
85
length : 0 ,
81
86
} ) ,
82
87
min_context_slot : None ,
83
- } ) ;
88
+ } ;
89
+ let mut pool = PubsubPool :: new ( ws_url, config) . await ?;
84
90
// Subscribe to the clock from the RPC (to figure out the latest slot)
85
- let ( mut clock_stream, clock_unsubscribe) = pubsub_client
86
- . account_subscribe ( & clock:: ID , rpc_account_info_config. clone ( ) )
87
- . await
88
- . map_err ( RemoteAccountUpdatesShardError :: PubsubClientError ) ?;
91
+ let mut clock_stream = pool. subscribe ( clock:: ID ) . await ?;
89
92
let mut clock_slot = 0 ;
90
93
// We'll store useful maps for each of the account subscriptions
91
94
let mut account_streams = StreamMap :: new ( ) ;
92
- // rust compiler is not yet smart enough to figure out the exact type
93
- type BoxFn = Box <
94
- dyn FnOnce ( ) -> Pin < Box < dyn Future < Output = ( ) > + Send + ' static > >
95
- + Send ,
96
- > ;
97
- let mut account_unsubscribes: HashMap < Pubkey , BoxFn > = HashMap :: new ( ) ;
98
95
const LOG_CLOCK_FREQ : u64 = 100 ;
99
96
let mut log_clock_count = 0 ;
100
97
@@ -118,19 +115,17 @@ impl RemoteAccountUpdatesShard {
118
115
} else {
119
116
warn!( "Shard {}: Received empty clock data" , self . shard_id) ;
120
117
}
118
+ self . try_to_override_last_known_update_slot( clock:: ID , clock_slot) ;
121
119
}
122
120
// When we receive a message to start monitoring an account
123
121
Some ( ( pubkey, unsub) ) = self . monitoring_request_receiver. recv( ) => {
124
122
if unsub {
125
- let Some ( request) = account_unsubscribes. remove( & pubkey) else {
126
- continue ;
127
- } ;
128
123
account_streams. remove( & pubkey) ;
129
124
metrics:: set_subscriptions_count( account_streams. len( ) , & self . shard_id) ;
130
- request ( ) . await ;
125
+ pool . unsubscribe ( & pubkey ) . await ;
131
126
continue ;
132
127
}
133
- if account_unsubscribes . contains_key ( & pubkey) {
128
+ if pool . subscribed ( & pubkey) {
134
129
continue ;
135
130
}
136
131
debug!(
@@ -139,12 +134,10 @@ impl RemoteAccountUpdatesShard {
139
134
pubkey,
140
135
clock_slot
141
136
) ;
142
- let ( stream, unsubscribe) = pubsub_client
143
- . account_subscribe( & pubkey, rpc_account_info_config. clone( ) )
144
- . await
145
- . map_err( RemoteAccountUpdatesShardError :: PubsubClientError ) ?;
137
+ let stream = pool
138
+ . subscribe( pubkey)
139
+ . await ?;
146
140
account_streams. insert( pubkey, stream) ;
147
- account_unsubscribes. insert( pubkey, unsubscribe) ;
148
141
metrics:: set_subscriptions_count( account_streams. len( ) , & self . shard_id) ;
149
142
self . try_to_override_first_subscribed_slot( pubkey, clock_slot) ;
150
143
}
@@ -164,17 +157,9 @@ impl RemoteAccountUpdatesShard {
164
157
}
165
158
}
166
159
// Cleanup all subscriptions and wait for proper shutdown
167
- for ( pubkey, account_unsubscribes) in account_unsubscribes. into_iter ( ) {
168
- info ! (
169
- "Shard {}: Account monitoring killed: {:?}" ,
170
- self . shard_id, pubkey
171
- ) ;
172
- account_unsubscribes ( ) . await ;
173
- }
174
- clock_unsubscribe ( ) . await ;
175
160
drop ( account_streams) ;
176
161
drop ( clock_stream) ;
177
- pubsub_client . shutdown ( ) . await ? ;
162
+ pool . shutdown ( ) . await ;
178
163
info ! ( "Shard {}: Stopped" , self . shard_id) ;
179
164
// Done
180
165
Ok ( ( ) )
@@ -236,3 +221,98 @@ impl RemoteAccountUpdatesShard {
236
221
}
237
222
}
238
223
}
224
+
225
+ struct PubsubPool {
226
+ clients : Vec < PubSubConnection > ,
227
+ unsubscribes : HashMap < Pubkey , ( usize , BoxFn ) > ,
228
+ config : RpcAccountInfoConfig ,
229
+ }
230
+
231
+ impl PubsubPool {
232
+ async fn new (
233
+ url : & str ,
234
+ config : RpcAccountInfoConfig ,
235
+ ) -> Result < Self , RemoteAccountUpdatesShardError > {
236
+ // 8 is pretty much arbitrary, but a sane value for the number
237
+ // of connections per RPC upstream, we don't overcomplicate things
238
+ // here, as the whole cloning pipeline will be rewritten quite soon
239
+ const CONNECTIONS_PER_POOL : usize = 8 ;
240
+ let mut clients = Vec :: with_capacity ( CONNECTIONS_PER_POOL ) ;
241
+ let mut connections: FuturesUnordered < _ > = ( 0 ..CONNECTIONS_PER_POOL )
242
+ . map ( |_| PubSubConnection :: new ( url) )
243
+ . collect ( ) ;
244
+ while let Some ( c) = connections. next ( ) . await {
245
+ clients. push ( c?) ;
246
+ }
247
+ Ok ( Self {
248
+ clients,
249
+ unsubscribes : HashMap :: new ( ) ,
250
+ config,
251
+ } )
252
+ }
253
+
254
+ async fn subscribe (
255
+ & mut self ,
256
+ pubkey : Pubkey ,
257
+ ) -> Result < SubscriptionStream , RemoteAccountUpdatesShardError > {
258
+ let ( index, client) = self
259
+ . clients
260
+ . iter_mut ( )
261
+ . enumerate ( )
262
+ . min_by ( |a, b| a. 1 . subs . cmp ( & b. 1 . subs ) )
263
+ . expect ( "clients vec is always greater than 0" ) ;
264
+ let ( stream, unsubscribe) = client
265
+ . inner
266
+ . account_subscribe ( & pubkey, Some ( self . config . clone ( ) ) )
267
+ . await
268
+ . map_err ( RemoteAccountUpdatesShardError :: PubsubClientError ) ?;
269
+ client. subs += 1 ;
270
+ // SAFETY:
271
+ // we never drop the PubsubPool before the returned subscription stream
272
+ // so the lifetime of the stream can be safely extended to 'static
273
+ #[ allow( clippy:: missing_transmute_annotations) ]
274
+ let stream = unsafe { std:: mem:: transmute ( stream) } ;
275
+ self . unsubscribes . insert ( pubkey, ( index, unsubscribe) ) ;
276
+ Ok ( stream)
277
+ }
278
+
279
+ async fn unsubscribe ( & mut self , pubkey : & Pubkey ) {
280
+ let Some ( ( index, callback) ) = self . unsubscribes . remove ( pubkey) else {
281
+ return ;
282
+ } ;
283
+ callback ( ) . await ;
284
+ let Some ( client) = self . clients . get_mut ( index) else {
285
+ return ;
286
+ } ;
287
+ client. subs = client. subs . saturating_sub ( 1 ) ;
288
+ }
289
+
290
+ fn subscribed ( & mut self , pubkey : & Pubkey ) -> bool {
291
+ self . unsubscribes . contains_key ( pubkey)
292
+ }
293
+
294
+ async fn shutdown ( & mut self ) {
295
+ // Cleanup all subscriptions and wait for proper shutdown
296
+ for ( pubkey, ( _, callback) ) in self . unsubscribes . drain ( ) {
297
+ info ! ( "Account monitoring killed: {:?}" , pubkey) ;
298
+ callback ( ) . await ;
299
+ }
300
+ for client in self . clients . drain ( ..) {
301
+ let _ = client. inner . shutdown ( ) . await ;
302
+ }
303
+ }
304
+ }
305
+
306
+ struct PubSubConnection {
307
+ inner : PubsubClient ,
308
+ subs : usize ,
309
+ }
310
+
311
+ impl PubSubConnection {
312
+ async fn new ( url : & str ) -> Result < Self , RemoteAccountUpdatesShardError > {
313
+ let inner = PubsubClient :: new ( url)
314
+ . await
315
+ . map_err ( RemoteAccountUpdatesShardError :: PubsubClientError ) ?;
316
+ Ok ( Self { inner, subs : 0 } )
317
+ }
318
+ }
0 commit comments