@@ -627,7 +627,7 @@ impl<E: EthSpec> fmt::Debug for Work<E> {
627
627
}
628
628
}
629
629
630
- #[ derive( IntoStaticStr , PartialEq , Eq , Debug ) ]
630
+ #[ derive( IntoStaticStr , PartialEq , Eq , Debug , Clone ) ]
631
631
#[ strum( serialize_all = "snake_case" ) ]
632
632
pub enum WorkType {
633
633
GossipAttestation ,
@@ -734,7 +734,7 @@ impl<E: EthSpec> Work<E> {
734
734
/// Unifies all the messages processed by the `BeaconProcessor`.
735
735
enum InboundEvent < E : EthSpec > {
736
736
/// A worker has completed a task and is free.
737
- WorkerIdle ,
737
+ WorkerIdle ( WorkType ) ,
738
738
/// There is new work to be done.
739
739
WorkEvent ( WorkEvent < E > ) ,
740
740
/// A work event that was queued for re-processing has become ready.
@@ -747,7 +747,7 @@ enum InboundEvent<E: EthSpec> {
747
747
/// control (specifically in the ordering of event processing).
748
748
struct InboundEvents < E : EthSpec > {
749
749
/// Used by workers when they finish a task.
750
- idle_rx : mpsc:: Receiver < ( ) > ,
750
+ idle_rx : mpsc:: Receiver < WorkType > ,
751
751
/// Used by upstream processes to send new work to the `BeaconProcessor`.
752
752
event_rx : mpsc:: Receiver < WorkEvent < E > > ,
753
753
/// Used internally for queuing work ready to be re-processed.
@@ -761,8 +761,8 @@ impl<E: EthSpec> Stream for InboundEvents<E> {
761
761
// Always check for idle workers before anything else. This allows us to ensure that a big
762
762
// stream of new events doesn't suppress the processing of existing events.
763
763
match self . idle_rx . poll_recv ( cx) {
764
- Poll :: Ready ( Some ( ( ) ) ) => {
765
- return Poll :: Ready ( Some ( InboundEvent :: WorkerIdle ) ) ;
764
+ Poll :: Ready ( Some ( work_type ) ) => {
765
+ return Poll :: Ready ( Some ( InboundEvent :: WorkerIdle ( work_type ) ) ) ;
766
766
}
767
767
Poll :: Ready ( None ) => {
768
768
return Poll :: Ready ( None ) ;
@@ -829,7 +829,7 @@ impl<E: EthSpec> BeaconProcessor<E> {
829
829
queue_lengths : BeaconProcessorQueueLengths ,
830
830
) -> Result < ( ) , String > {
831
831
// Used by workers to communicate that they are finished a task.
832
- let ( idle_tx, idle_rx) = mpsc:: channel :: < ( ) > ( MAX_IDLE_QUEUE_LEN ) ;
832
+ let ( idle_tx, idle_rx) = mpsc:: channel :: < WorkType > ( MAX_IDLE_QUEUE_LEN ) ;
833
833
834
834
// Using LIFO queues for attestations since validator profits rely upon getting fresh
835
835
// attestations into blocks. Additionally, later attestations contain more information than
@@ -931,8 +931,12 @@ impl<E: EthSpec> BeaconProcessor<E> {
931
931
932
932
loop {
933
933
let work_event = match inbound_events. next ( ) . await {
934
- Some ( InboundEvent :: WorkerIdle ) => {
935
- self . current_workers = self . current_workers . saturating_sub ( 1 ) ;
934
+ Some ( InboundEvent :: WorkerIdle ( work_type) ) => {
935
+ let threads_freed = match work_type {
936
+ WorkType :: ColumnReconstruction => 4 ,
937
+ _ => 1 ,
938
+ } ;
939
+ self . current_workers = self . current_workers . saturating_sub ( threads_freed) ;
936
940
None
937
941
}
938
942
Some ( InboundEvent :: WorkEvent ( event) ) if enable_backfill_rate_limiting => {
@@ -1007,6 +1011,7 @@ impl<E: EthSpec> BeaconProcessor<E> {
1007
1011
}
1008
1012
1009
1013
let can_spawn = self . current_workers < self . config . max_workers ;
1014
+ let can_spawn_extra_threads = self . current_workers < self . config . max_workers + 4 ;
1010
1015
let drop_during_sync = work_event
1011
1016
. as_ref ( )
1012
1017
. is_some_and ( |event| event. drop_during_sync ) ;
@@ -1245,7 +1250,30 @@ impl<E: EthSpec> BeaconProcessor<E> {
1245
1250
1246
1251
if let Some ( work_event) = work_event {
1247
1252
let work_type = work_event. to_type ( ) ;
1248
- self . spawn_worker ( work_event, idle_tx) ;
1253
+ let thread_count = match work_type {
1254
+ WorkType :: ColumnReconstruction => 4 ,
1255
+ _ => 1 ,
1256
+ } ;
1257
+ self . spawn_worker ( work_event, idle_tx, thread_count) ;
1258
+ Some ( work_type)
1259
+ } else {
1260
+ None
1261
+ }
1262
+ }
1263
+ None if can_spawn_extra_threads => {
1264
+ let work_event: Option < Work < E > > =
1265
+ if let Some ( item) = column_reconstruction_queue. pop ( ) {
1266
+ Some ( item)
1267
+ } else {
1268
+ None
1269
+ } ;
1270
+ if let Some ( work_event) = work_event {
1271
+ let work_type = work_event. to_type ( ) ;
1272
+ let thread_count = match work_type {
1273
+ WorkType :: ColumnReconstruction => 4 ,
1274
+ _ => 1 ,
1275
+ } ;
1276
+ self . spawn_worker ( work_event, idle_tx, thread_count) ;
1249
1277
Some ( work_type)
1250
1278
} else {
1251
1279
None
@@ -1293,7 +1321,20 @@ impl<E: EthSpec> BeaconProcessor<E> {
1293
1321
)
1294
1322
}
1295
1323
}
1296
- _ if can_spawn => self . spawn_worker ( work, idle_tx) ,
1324
+ _ if can_spawn => {
1325
+ let thread_count = match work. to_type ( ) {
1326
+ WorkType :: ColumnReconstruction => 4 ,
1327
+ _ => 1 ,
1328
+ } ;
1329
+ self . spawn_worker ( work, idle_tx, thread_count) ;
1330
+ }
1331
+ _ if can_spawn_extra_threads => {
1332
+ let thread_count = match work. to_type ( ) {
1333
+ WorkType :: ColumnReconstruction => 4 ,
1334
+ _ => 1 ,
1335
+ } ;
1336
+ self . spawn_worker ( work, idle_tx, thread_count)
1337
+ }
1297
1338
Work :: GossipAttestation { .. } => attestation_queue. push ( work) ,
1298
1339
// Attestation batches are formed internally within the
1299
1340
// `BeaconProcessor`, they are not sent from external services.
@@ -1486,7 +1527,12 @@ impl<E: EthSpec> BeaconProcessor<E> {
1486
1527
/// Spawns a blocking worker thread to process some `Work`.
1487
1528
///
1488
1529
/// Sends an message on `idle_tx` when the work is complete and the task is stopping.
1489
- fn spawn_worker ( & mut self , work : Work < E > , idle_tx : mpsc:: Sender < ( ) > ) {
1530
+ fn spawn_worker (
1531
+ & mut self ,
1532
+ work : Work < E > ,
1533
+ idle_tx : mpsc:: Sender < WorkType > ,
1534
+ thread_count : usize ,
1535
+ ) {
1490
1536
let work_id = work. str_id ( ) ;
1491
1537
let worker_timer =
1492
1538
metrics:: start_timer_vec ( & metrics:: BEACON_PROCESSOR_WORKER_TIME , & [ work_id] ) ;
@@ -1502,11 +1548,12 @@ impl<E: EthSpec> BeaconProcessor<E> {
1502
1548
// As such, this instantiation should happen as early in the function as possible.
1503
1549
let send_idle_on_drop = SendOnDrop {
1504
1550
tx : idle_tx,
1551
+ work_type : work. to_type ( ) ,
1505
1552
_worker_timer : worker_timer,
1506
1553
} ;
1507
1554
1508
1555
let worker_id = self . current_workers ;
1509
- self . current_workers = self . current_workers . saturating_add ( 1 ) ;
1556
+ self . current_workers = self . current_workers . saturating_add ( thread_count ) ;
1510
1557
1511
1558
let executor = self . executor . clone ( ) ;
1512
1559
@@ -1655,14 +1702,16 @@ impl TaskSpawner {
1655
1702
///
1656
1703
/// https://doc.rust-lang.org/std/ops/trait.Drop.html#panics
1657
1704
pub struct SendOnDrop {
1658
- tx : mpsc:: Sender < ( ) > ,
1705
+ tx : mpsc:: Sender < WorkType > ,
1706
+ work_type : WorkType ,
1659
1707
// The field is unused, but it's here to ensure the timer is dropped once the task has finished.
1660
1708
_worker_timer : Option < metrics:: HistogramTimer > ,
1661
1709
}
1662
1710
1663
1711
impl Drop for SendOnDrop {
1664
1712
fn drop ( & mut self ) {
1665
- if let Err ( e) = self . tx . try_send ( ( ) ) {
1713
+ let work_type = self . work_type . clone ( ) ;
1714
+ if let Err ( e) = self . tx . try_send ( work_type) {
1666
1715
warn ! (
1667
1716
msg = "did not free worker, shutdown may be underway" ,
1668
1717
error = %e,
0 commit comments