21
21
hash:: Hash , nonce_account, pubkey:: Pubkey , saturating_add_assign, signature:: Signature ,
22
22
} ,
23
23
std:: {
24
- collections:: {
25
- hash_map:: { Entry , HashMap } ,
26
- HashSet ,
27
- } ,
24
+ collections:: hash_map:: { Entry , HashMap } ,
28
25
net:: SocketAddr ,
29
26
sync:: {
30
27
atomic:: { AtomicBool , Ordering } ,
@@ -99,6 +96,16 @@ impl TransactionInfo {
99
96
last_sent_time,
100
97
}
101
98
}
99
+
100
+ fn get_max_retries (
101
+ & self ,
102
+ default_max_retries : Option < usize > ,
103
+ service_max_retries : usize ,
104
+ ) -> Option < usize > {
105
+ self . max_retries
106
+ . or ( default_max_retries)
107
+ . map ( |max_retries| max_retries. min ( service_max_retries) )
108
+ }
102
109
}
103
110
104
111
#[ derive( Default , Debug , PartialEq , Eq ) ]
@@ -109,6 +116,7 @@ struct ProcessTransactionsResult {
109
116
max_retries_elapsed : u64 ,
110
117
failed : u64 ,
111
118
retained : u64 ,
119
+ last_sent_time : Option < Instant > ,
112
120
}
113
121
114
122
#[ derive( Clone , Debug ) ]
@@ -239,6 +247,8 @@ impl SendTransactionService {
239
247
batch_send_rate_ms,
240
248
batch_size,
241
249
retry_pool_max_size,
250
+ default_max_retries,
251
+ service_max_retries,
242
252
..
243
253
} : Config ,
244
254
stats_report : Arc < SendTransactionServiceStatsReport > ,
@@ -301,9 +311,17 @@ impl SendTransactionService {
301
311
{
302
312
// take a lock of retry_transactions and move the batch to the retry set.
303
313
let mut retry_transactions = retry_transactions. lock ( ) . unwrap ( ) ;
304
- let transactions_to_retry = transactions . len ( ) ;
314
+ let mut transactions_to_retry: usize = 0 ;
305
315
let mut transactions_added_to_retry: usize = 0 ;
306
316
for ( signature, mut transaction_info) in transactions. drain ( ) {
317
+ // drop transactions with 0 max retries
318
+ let max_retries = transaction_info
319
+ . get_max_retries ( default_max_retries, service_max_retries) ;
320
+ if max_retries == Some ( 0 ) {
321
+ continue ;
322
+ }
323
+ transactions_to_retry += 1 ;
324
+
307
325
let retry_len = retry_transactions. len ( ) ;
308
326
let entry = retry_transactions. entry ( signature) ;
309
327
if let Entry :: Vacant ( _) = entry {
@@ -342,19 +360,20 @@ impl SendTransactionService {
342
360
exit : Arc < AtomicBool > ,
343
361
) -> JoinHandle < ( ) > {
344
362
debug ! ( "Starting send-transaction-service::retry_thread." ) ;
363
+ let retry_interval_ms_default = MAX_RETRY_SLEEP_MS . min ( config. retry_rate_ms ) ;
364
+ let mut retry_interval_ms = retry_interval_ms_default;
345
365
Builder :: new ( )
346
366
. name ( "solStxRetry" . to_string ( ) )
347
367
. spawn ( move || loop {
348
- let retry_interval_ms = config. retry_rate_ms ;
349
- let stats = & stats_report. stats ;
350
- sleep ( Duration :: from_millis (
351
- MAX_RETRY_SLEEP_MS . min ( retry_interval_ms) ,
352
- ) ) ;
368
+ sleep ( Duration :: from_millis ( retry_interval_ms) ) ;
353
369
if exit. load ( Ordering :: Relaxed ) {
354
370
break ;
355
371
}
356
372
let mut transactions = retry_transactions. lock ( ) . unwrap ( ) ;
357
- if !transactions. is_empty ( ) {
373
+ if transactions. is_empty ( ) {
374
+ retry_interval_ms = retry_interval_ms_default;
375
+ } else {
376
+ let stats = & stats_report. stats ;
358
377
stats
359
378
. retry_queue_size
360
379
. store ( transactions. len ( ) as u64 , Ordering :: Relaxed ) ;
@@ -363,7 +382,7 @@ impl SendTransactionService {
363
382
( bank_forks. root_bank ( ) , bank_forks. working_bank ( ) )
364
383
} ;
365
384
366
- let _result = Self :: process_transactions (
385
+ let result = Self :: process_transactions (
367
386
& working_bank,
368
387
& root_bank,
369
388
& mut transactions,
@@ -372,6 +391,17 @@ impl SendTransactionService {
372
391
stats,
373
392
) ;
374
393
stats_report. report ( ) ;
394
+
395
+ // Adjust retry interval taking into account the time since the last send.
396
+ retry_interval_ms = retry_interval_ms_default
397
+ . checked_sub (
398
+ result
399
+ . last_sent_time
400
+ . and_then ( |last| Instant :: now ( ) . checked_duration_since ( last) )
401
+ . and_then ( |interval| interval. as_millis ( ) . try_into ( ) . ok ( ) )
402
+ . unwrap_or ( 0 ) ,
403
+ )
404
+ . unwrap_or ( retry_interval_ms_default) ;
375
405
}
376
406
} )
377
407
. unwrap ( )
@@ -394,7 +424,8 @@ impl SendTransactionService {
394
424
) -> ProcessTransactionsResult {
395
425
let mut result = ProcessTransactionsResult :: default ( ) ;
396
426
397
- let mut batched_transactions = HashSet :: new ( ) ;
427
+ let mut batched_transactions = Vec :: new ( ) ;
428
+ let mut exceeded_retries_transactions = Vec :: new ( ) ;
398
429
let retry_rate = Duration :: from_millis ( retry_rate_ms) ;
399
430
400
431
transactions. retain ( |signature, transaction_info| {
@@ -413,7 +444,8 @@ impl SendTransactionService {
413
444
let now = Instant :: now ( ) ;
414
445
let expired = transaction_info
415
446
. last_sent_time
416
- . map ( |last| now. duration_since ( last) >= retry_rate)
447
+ . and_then ( |last| now. checked_duration_since ( last) )
448
+ . map ( |elapsed| elapsed >= retry_rate)
417
449
. unwrap_or ( false ) ;
418
450
let verify_nonce_account =
419
451
nonce_account:: verify_nonce_account ( & nonce_account, & durable_nonce) ;
@@ -431,10 +463,8 @@ impl SendTransactionService {
431
463
return false ;
432
464
}
433
465
434
- let max_retries = transaction_info
435
- . max_retries
436
- . or ( default_max_retries)
437
- . map ( |max_retries| max_retries. min ( service_max_retries) ) ;
466
+ let max_retries =
467
+ transaction_info. get_max_retries ( default_max_retries, service_max_retries) ;
438
468
439
469
if let Some ( max_retries) = max_retries {
440
470
if transaction_info. retries >= max_retries {
@@ -452,21 +482,36 @@ impl SendTransactionService {
452
482
let now = Instant :: now ( ) ;
453
483
let need_send = transaction_info
454
484
. last_sent_time
455
- . map ( |last| now. duration_since ( last) >= retry_rate)
485
+ . and_then ( |last| now. checked_duration_since ( last) )
486
+ . map ( |elapsed| elapsed >= retry_rate)
456
487
. unwrap_or ( true ) ;
457
488
if need_send {
458
489
if transaction_info. last_sent_time . is_some ( ) {
459
490
// Transaction sent before is unknown to the working bank, it might have been
460
- // dropped or landed in another fork. Re-send it
491
+ // dropped or landed in another fork. Re-send it.
461
492
462
493
info ! ( "Retrying transaction: {}" , signature) ;
463
494
result. retried += 1 ;
464
495
transaction_info. retries += 1 ;
465
- stats. retries . fetch_add ( 1 , Ordering :: Relaxed ) ;
466
496
}
467
497
468
- batched_transactions. insert ( * signature) ;
498
+ batched_transactions. push ( * signature) ;
469
499
transaction_info. last_sent_time = Some ( now) ;
500
+
501
+ let max_retries = transaction_info
502
+ . get_max_retries ( default_max_retries, service_max_retries) ;
503
+ if let Some ( max_retries) = max_retries {
504
+ if transaction_info. retries >= max_retries {
505
+ exceeded_retries_transactions. push ( * signature) ;
506
+ }
507
+ }
508
+ } else if let Some ( last) = transaction_info. last_sent_time {
509
+ result. last_sent_time = Some (
510
+ result
511
+ . last_sent_time
512
+ . map ( |result_last| result_last. min ( last) )
513
+ . unwrap_or ( last) ,
514
+ ) ;
470
515
}
471
516
true
472
517
}
@@ -484,19 +529,31 @@ impl SendTransactionService {
484
529
}
485
530
} ) ;
486
531
532
+ stats. retries . fetch_add ( result. retried , Ordering :: Relaxed ) ;
533
+
487
534
if !batched_transactions. is_empty ( ) {
488
535
// Processing the transactions in batch
489
- let wire_transactions = transactions
536
+ let wire_transactions = batched_transactions
490
537
. iter ( )
491
- . filter ( | ( signature, _ ) | batched_transactions . contains ( signature) )
492
- . map ( |( _ , transaction_info) | transaction_info. wire_transaction . clone ( ) ) ;
538
+ . filter_map ( | signature| transactions . get ( signature) )
539
+ . map ( |transaction_info| transaction_info. wire_transaction . clone ( ) ) ;
493
540
494
541
let iter = wire_transactions. chunks ( batch_size) ;
495
542
for chunk in & iter {
496
543
let chunk = chunk. collect ( ) ;
497
544
client. send_transactions_in_batch ( chunk, stats) ;
498
545
}
499
546
}
547
+
548
+ result. max_retries_elapsed += exceeded_retries_transactions. len ( ) as u64 ;
549
+ stats
550
+ . transactions_exceeding_max_retries
551
+ . fetch_add ( result. max_retries_elapsed , Ordering :: Relaxed ) ;
552
+ for signature in exceeded_retries_transactions {
553
+ info ! ( "Dropping transaction due to max retries: {signature}" ) ;
554
+ transactions. remove ( & signature) ;
555
+ }
556
+
500
557
result
501
558
}
502
559
@@ -848,28 +905,12 @@ mod test {
848
905
& config,
849
906
& stats,
850
907
) ;
851
- assert_eq ! ( transactions. len( ) , 1 ) ;
852
- assert_eq ! (
853
- result,
854
- ProcessTransactionsResult {
855
- retried: 1 ,
856
- max_retries_elapsed: 1 ,
857
- ..ProcessTransactionsResult :: default ( )
858
- }
859
- ) ;
860
- let result = SendTransactionService :: process_transactions (
861
- & working_bank,
862
- & root_bank,
863
- & mut transactions,
864
- & client,
865
- & config,
866
- & stats,
867
- ) ;
868
908
assert ! ( transactions. is_empty( ) ) ;
869
909
assert_eq ! (
870
910
result,
871
911
ProcessTransactionsResult {
872
- max_retries_elapsed: 1 ,
912
+ retried: 1 ,
913
+ max_retries_elapsed: 2 ,
873
914
..ProcessTransactionsResult :: default ( )
874
915
}
875
916
) ;
0 commit comments