1#[cfg(feature = "dev-context-only-utils")]
16use qualifier_attr::qualifiers;
17use {
18 assert_matches::assert_matches,
19 clone_agave_banking_stage_ingress_types::{BankingPacketBatch, BankingPacketReceiver},
20 clone_solana_ledger::blockstore_processor::{
21 execute_batch, TransactionBatchWithIndexes, TransactionStatusSender,
22 },
23 clone_solana_poh::poh_recorder::{RecordTransactionsSummary, TransactionRecorder},
24 clone_solana_pubkey::Pubkey,
25 clone_solana_runtime::{
26 installed_scheduler_pool::{
27 initialized_result_with_timings, InstalledScheduler, InstalledSchedulerBox,
28 InstalledSchedulerPool, InstalledSchedulerPoolArc, ResultWithTimings, ScheduleResult,
29 SchedulerAborted, SchedulerId, SchedulingContext, TimeoutListener,
30 UninstalledScheduler, UninstalledSchedulerBox,
31 },
32 prioritization_fee_cache::PrioritizationFeeCache,
33 vote_sender_types::ReplayVoteSender,
34 },
35 clone_solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
36 clone_solana_timings::ExecuteTimings,
37 clone_solana_transaction::sanitized::SanitizedTransaction,
38 clone_solana_transaction_error::{TransactionError, TransactionResult as Result},
39 clone_solana_unified_scheduler_logic::{
40 SchedulingMode::{self, BlockProduction, BlockVerification},
41 SchedulingStateMachine, Task, UsageQueue,
42 },
43 crossbeam_channel::{self, never, select_biased, Receiver, RecvError, SendError, Sender},
44 dashmap::DashMap,
45 derive_where::derive_where,
46 dyn_clone::{clone_trait_object, DynClone},
47 log::*,
48 scopeguard::defer,
49 static_assertions::const_assert_eq,
50 std::{
51 fmt::Debug,
52 marker::PhantomData,
53 mem,
54 sync::{
55 atomic::{AtomicU64, AtomicUsize, Ordering::Relaxed},
56 Arc, Mutex, OnceLock, Weak,
57 },
58 thread::{self, sleep, JoinHandle},
59 time::{Duration, Instant},
60 },
61 trait_set::trait_set,
62 vec_extract_if_polyfill::MakeExtractIf,
63};
64
65mod sleepless_testing;
66use crate::sleepless_testing::BuilderTracked;
67
68#[allow(dead_code)]
70#[derive(Debug)]
71enum CheckPoint {
72 NewTask(usize),
73 TaskHandled(usize),
74 SchedulerThreadAborted,
75 IdleSchedulerCleaned(usize),
76 TrashedSchedulerCleaned(usize),
77 TimeoutListenerTriggered(usize),
78}
79
80type AtomicSchedulerId = AtomicU64;
81
82#[derive(Debug)]
97pub struct SchedulerPool<S: SpawnableScheduler<TH>, TH: TaskHandler> {
98 scheduler_inners: Mutex<Vec<(S::Inner, Instant)>>,
99 trashed_scheduler_inners: Mutex<Vec<S::Inner>>,
100 timeout_listeners: Mutex<Vec<(TimeoutListener, Instant)>>,
101 handler_count: usize,
102 common_handler_context: CommonHandlerContext,
103 banking_stage_handler_context: Mutex<Option<BankingStageHandlerContext>>,
104 weak_self: Weak<Self>,
115 next_scheduler_id: AtomicSchedulerId,
116 max_usage_queue_count: usize,
117 _phantom: PhantomData<TH>,
118}
119
120#[derive(derive_more::Debug, Clone)]
121pub struct HandlerContext {
122 log_messages_bytes_limit: Option<usize>,
123 transaction_status_sender: Option<TransactionStatusSender>,
124 replay_vote_sender: Option<ReplayVoteSender>,
125 prioritization_fee_cache: Arc<PrioritizationFeeCache>,
126 banking_packet_receiver: BankingPacketReceiver,
127 #[debug("{banking_packet_handler:p}")]
128 banking_packet_handler: Box<dyn BankingPacketHandler>,
129 banking_stage_helper: Option<Arc<BankingStageHelper>>,
130 transaction_recorder: Option<TransactionRecorder>,
131}
132
133#[derive(Debug, Clone)]
134struct CommonHandlerContext {
135 log_messages_bytes_limit: Option<usize>,
136 transaction_status_sender: Option<TransactionStatusSender>,
137 replay_vote_sender: Option<ReplayVoteSender>,
138 prioritization_fee_cache: Arc<PrioritizationFeeCache>,
139}
140
141impl CommonHandlerContext {
142 fn into_handler_context(
143 self,
144 banking_packet_receiver: BankingPacketReceiver,
145 banking_packet_handler: Box<dyn BankingPacketHandler>,
146 banking_stage_helper: Option<Arc<BankingStageHelper>>,
147 transaction_recorder: Option<TransactionRecorder>,
148 ) -> HandlerContext {
149 let Self {
150 log_messages_bytes_limit,
151 transaction_status_sender,
152 replay_vote_sender,
153 prioritization_fee_cache,
154 } = self;
155
156 HandlerContext {
157 log_messages_bytes_limit,
158 transaction_status_sender,
159 replay_vote_sender,
160 prioritization_fee_cache,
161 banking_packet_receiver,
162 banking_packet_handler,
163 banking_stage_helper,
164 transaction_recorder,
165 }
166 }
167}
168
169#[derive(derive_more::Debug)]
170struct BankingStageHandlerContext {
171 banking_packet_receiver: BankingPacketReceiver,
172 #[debug("{banking_packet_handler:p}")]
173 banking_packet_handler: Box<dyn BankingPacketHandler>,
174 transaction_recorder: TransactionRecorder,
175}
176
177trait_set! {
178 pub trait BankingPacketHandler =
179 DynClone + FnMut(&BankingStageHelper, BankingPacketBatch) + Send + 'static;
180}
181clone_trait_object!(BankingPacketHandler);
183
184#[derive(Debug)]
185pub struct BankingStageHelper {
186 usage_queue_loader: UsageQueueLoader,
187 next_task_id: AtomicUsize,
188 new_task_sender: Sender<NewTaskPayload>,
189}
190
191impl BankingStageHelper {
192 fn new(new_task_sender: Sender<NewTaskPayload>) -> Self {
193 Self {
194 usage_queue_loader: UsageQueueLoader::default(),
195 next_task_id: AtomicUsize::default(),
196 new_task_sender,
197 }
198 }
199
200 pub fn generate_task_ids(&self, count: usize) -> usize {
201 self.next_task_id.fetch_add(count, Relaxed)
202 }
203
204 pub fn create_new_task(
205 &self,
206 transaction: RuntimeTransaction<SanitizedTransaction>,
207 index: usize,
208 ) -> Task {
209 SchedulingStateMachine::create_task(transaction, index, &mut |pubkey| {
210 self.usage_queue_loader.load(pubkey)
211 })
212 }
213
214 pub fn send_new_task(&self, task: Task) {
215 self.new_task_sender
216 .send(NewTaskPayload::Payload(task))
217 .unwrap();
218 }
219}
220
221pub type DefaultSchedulerPool =
222 SchedulerPool<PooledScheduler<DefaultTaskHandler>, DefaultTaskHandler>;
223
224const DEFAULT_POOL_CLEANER_INTERVAL: Duration = Duration::from_secs(10);
225const DEFAULT_MAX_POOLING_DURATION: Duration = Duration::from_secs(180);
226const DEFAULT_TIMEOUT_DURATION: Duration = Duration::from_secs(12);
227const DEFAULT_MAX_USAGE_QUEUE_COUNT: usize = 262_144;
242
243impl<S, TH> SchedulerPool<S, TH>
244where
245 S: SpawnableScheduler<TH>,
246 TH: TaskHandler,
247{
248 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
251 fn new(
252 handler_count: Option<usize>,
253 log_messages_bytes_limit: Option<usize>,
254 transaction_status_sender: Option<TransactionStatusSender>,
255 replay_vote_sender: Option<ReplayVoteSender>,
256 prioritization_fee_cache: Arc<PrioritizationFeeCache>,
257 ) -> Arc<Self> {
258 Self::do_new(
259 handler_count,
260 log_messages_bytes_limit,
261 transaction_status_sender,
262 replay_vote_sender,
263 prioritization_fee_cache,
264 DEFAULT_POOL_CLEANER_INTERVAL,
265 DEFAULT_MAX_POOLING_DURATION,
266 DEFAULT_MAX_USAGE_QUEUE_COUNT,
267 DEFAULT_TIMEOUT_DURATION,
268 )
269 }
270
271 fn do_new(
272 handler_count: Option<usize>,
273 log_messages_bytes_limit: Option<usize>,
274 transaction_status_sender: Option<TransactionStatusSender>,
275 replay_vote_sender: Option<ReplayVoteSender>,
276 prioritization_fee_cache: Arc<PrioritizationFeeCache>,
277 pool_cleaner_interval: Duration,
278 max_pooling_duration: Duration,
279 max_usage_queue_count: usize,
280 timeout_duration: Duration,
281 ) -> Arc<Self> {
282 let handler_count = handler_count.unwrap_or(Self::default_handler_count());
283 assert!(handler_count >= 1);
284
285 let scheduler_pool = Arc::new_cyclic(|weak_self| Self {
286 scheduler_inners: Mutex::default(),
287 trashed_scheduler_inners: Mutex::default(),
288 timeout_listeners: Mutex::default(),
289 handler_count,
290 common_handler_context: CommonHandlerContext {
291 log_messages_bytes_limit,
292 transaction_status_sender,
293 replay_vote_sender,
294 prioritization_fee_cache,
295 },
296 banking_stage_handler_context: Mutex::default(),
297 weak_self: weak_self.clone(),
298 next_scheduler_id: AtomicSchedulerId::default(),
299 max_usage_queue_count,
300 _phantom: PhantomData,
301 });
302
303 let cleaner_main_loop = {
304 let weak_scheduler_pool = Arc::downgrade(&scheduler_pool);
305
306 move || loop {
307 sleep(pool_cleaner_interval);
308
309 let Some(scheduler_pool) = weak_scheduler_pool.upgrade() else {
310 break;
311 };
312
313 let now = Instant::now();
314
315 let idle_inner_count = {
316 let mut idle_inners = Vec::with_capacity(128);
318
319 let Ok(mut scheduler_inners) = scheduler_pool.scheduler_inners.lock() else {
320 break;
321 };
322 #[allow(unstable_name_collisions)]
330 idle_inners.extend(scheduler_inners.extract_if(|(_inner, pooled_at)| {
331 now.duration_since(*pooled_at) > max_pooling_duration
332 }));
333 drop(scheduler_inners);
334
335 let idle_inner_count = idle_inners.len();
336 drop(idle_inners);
337 idle_inner_count
338 };
339
340 let trashed_inner_count = {
341 let Ok(mut trashed_scheduler_inners) =
342 scheduler_pool.trashed_scheduler_inners.lock()
343 else {
344 break;
345 };
346 let trashed_inners: Vec<_> = mem::take(&mut *trashed_scheduler_inners);
347 drop(trashed_scheduler_inners);
348
349 let trashed_inner_count = trashed_inners.len();
350 drop(trashed_inners);
351 trashed_inner_count
352 };
353
354 let triggered_timeout_listener_count = {
355 let mut expired_listeners = Vec::with_capacity(128);
357 let Ok(mut timeout_listeners) = scheduler_pool.timeout_listeners.lock() else {
358 break;
359 };
360 #[allow(unstable_name_collisions)]
361 expired_listeners.extend(timeout_listeners.extract_if(
362 |(_callback, registered_at)| {
363 now.duration_since(*registered_at) > timeout_duration
364 },
365 ));
366 drop(timeout_listeners);
367
368 let count = expired_listeners.len();
369 for (timeout_listener, _registered_at) in expired_listeners {
373 timeout_listener.trigger(scheduler_pool.clone());
374 }
375 count
376 };
377
378 info!(
379 "Scheduler pool cleaner: dropped {} idle inners, {} trashed inners, triggered {} timeout listeners",
380 idle_inner_count, trashed_inner_count, triggered_timeout_listener_count,
381 );
382 sleepless_testing::at(CheckPoint::IdleSchedulerCleaned(idle_inner_count));
383 sleepless_testing::at(CheckPoint::TrashedSchedulerCleaned(trashed_inner_count));
384 sleepless_testing::at(CheckPoint::TimeoutListenerTriggered(
385 triggered_timeout_listener_count,
386 ));
387 }
388 };
389
390 thread::Builder::new()
392 .name("solScCleaner".to_owned())
393 .spawn_tracked(cleaner_main_loop)
394 .unwrap();
395
396 scheduler_pool
397 }
398
399 pub fn new_dyn(
402 handler_count: Option<usize>,
403 log_messages_bytes_limit: Option<usize>,
404 transaction_status_sender: Option<TransactionStatusSender>,
405 replay_vote_sender: Option<ReplayVoteSender>,
406 prioritization_fee_cache: Arc<PrioritizationFeeCache>,
407 ) -> InstalledSchedulerPoolArc {
408 Self::new(
409 handler_count,
410 log_messages_bytes_limit,
411 transaction_status_sender,
412 replay_vote_sender,
413 prioritization_fee_cache,
414 )
415 }
416
417 fn self_arc(&self) -> Arc<Self> {
419 self.weak_self
420 .upgrade()
421 .expect("self-referencing Arc-ed pool")
422 }
423
424 fn new_scheduler_id(&self) -> SchedulerId {
425 self.next_scheduler_id.fetch_add(1, Relaxed)
426 }
427
428 fn return_scheduler(&self, scheduler: S::Inner) {
431 let should_trash = scheduler.is_trashed();
434 if should_trash {
435 self.trashed_scheduler_inners
440 .lock()
441 .expect("not poisoned")
442 .push(scheduler);
443 } else {
444 self.scheduler_inners
445 .lock()
446 .expect("not poisoned")
447 .push((scheduler, Instant::now()));
448 }
449 }
450
451 #[cfg(test)]
452 fn do_take_scheduler(&self, context: SchedulingContext) -> S {
453 self.do_take_resumed_scheduler(context, initialized_result_with_timings())
454 }
455
456 fn do_take_resumed_scheduler(
457 &self,
458 context: SchedulingContext,
459 result_with_timings: ResultWithTimings,
460 ) -> S {
461 assert_matches!(result_with_timings, (Ok(_), _));
462
463 if let Some((inner, _pooled_at)) = self.scheduler_inners.lock().expect("not poisoned").pop()
466 {
467 S::from_inner(inner, context, result_with_timings)
468 } else {
469 S::spawn(self.self_arc(), context, result_with_timings)
470 }
471 }
472
473 #[cfg(feature = "dev-context-only-utils")]
474 pub fn pooled_scheduler_count(&self) -> usize {
475 self.scheduler_inners.lock().expect("not poisoned").len()
476 }
477
478 pub fn register_banking_stage(
479 &self,
480 banking_packet_receiver: BankingPacketReceiver,
481 banking_packet_handler: Box<dyn BankingPacketHandler>,
482 transaction_recorder: TransactionRecorder,
483 ) {
484 *self.banking_stage_handler_context.lock().unwrap() = Some(BankingStageHandlerContext {
485 banking_packet_receiver,
486 banking_packet_handler,
487 transaction_recorder,
488 });
489 }
490
491 fn create_handler_context(
492 &self,
493 mode: SchedulingMode,
494 new_task_sender: &Sender<NewTaskPayload>,
495 ) -> HandlerContext {
496 let (
497 banking_packet_receiver,
498 banking_packet_handler,
499 banking_stage_helper,
500 transaction_recorder,
501 ): (
502 _,
503 Box<dyn BankingPacketHandler>, _,
505 _,
506 ) = match mode {
507 BlockVerification => {
508 (never(), Box::new(|_, _| {}), None, None)
510 }
511 BlockProduction => {
512 let handler_context = self.banking_stage_handler_context.lock().unwrap();
513 let handler_context = handler_context.as_ref().unwrap();
514
515 (
516 handler_context.banking_packet_receiver.clone(),
517 handler_context.banking_packet_handler.clone(),
518 Some(Arc::new(BankingStageHelper::new(new_task_sender.clone()))),
519 Some(handler_context.transaction_recorder.clone()),
520 )
521 }
522 };
523 self.common_handler_context.clone().into_handler_context(
524 banking_packet_receiver,
525 banking_packet_handler,
526 banking_stage_helper,
527 transaction_recorder,
528 )
529 }
530
531 pub fn default_handler_count() -> usize {
532 Self::calculate_default_handler_count(
533 thread::available_parallelism()
534 .ok()
535 .map(|non_zero| non_zero.get()),
536 )
537 }
538
539 pub fn calculate_default_handler_count(detected_cpu_core_count: Option<usize>) -> usize {
540 detected_cpu_core_count
546 .map(|core_count| (core_count / 4).max(1))
547 .unwrap_or(4)
548 }
549
550 pub fn cli_message() -> &'static str {
551 static MESSAGE: OnceLock<String> = OnceLock::new();
552
553 MESSAGE.get_or_init(|| {
554 format!(
555 "Change the number of the unified scheduler's transaction execution threads \
556 dedicated to each block, otherwise calculated as cpu_cores/4 [default: {}]",
557 Self::default_handler_count()
558 )
559 })
560 }
561}
562
563impl<S, TH> InstalledSchedulerPool for SchedulerPool<S, TH>
564where
565 S: SpawnableScheduler<TH>,
566 TH: TaskHandler,
567{
568 fn take_resumed_scheduler(
569 &self,
570 context: SchedulingContext,
571 result_with_timings: ResultWithTimings,
572 ) -> InstalledSchedulerBox {
573 Box::new(self.do_take_resumed_scheduler(context, result_with_timings))
574 }
575
576 fn register_timeout_listener(&self, timeout_listener: TimeoutListener) {
577 self.timeout_listeners
578 .lock()
579 .unwrap()
580 .push((timeout_listener, Instant::now()));
581 }
582}
583
584pub trait TaskHandler: Send + Sync + Debug + Sized + 'static {
585 fn handle(
586 result: &mut Result<()>,
587 timings: &mut ExecuteTimings,
588 scheduling_context: &SchedulingContext,
589 task: &Task,
590 handler_context: &HandlerContext,
591 );
592}
593
594#[derive(Debug)]
595pub struct DefaultTaskHandler;
596
597impl TaskHandler for DefaultTaskHandler {
598 fn handle(
599 result: &mut Result<()>,
600 timings: &mut ExecuteTimings,
601 scheduling_context: &SchedulingContext,
602 task: &Task,
603 handler_context: &HandlerContext,
604 ) {
605 let bank = scheduling_context.bank();
608 let transaction = task.transaction();
609 let index = task.task_index();
610
611 let batch = bank.prepare_unlocked_batch_from_single_tx(transaction);
612 let transaction_indexes = match scheduling_context.mode() {
613 BlockVerification => vec![index],
614 BlockProduction => {
615 vec![]
625 }
626 };
627 let batch_with_indexes = TransactionBatchWithIndexes {
628 batch,
629 transaction_indexes,
630 };
631
632 let pre_commit_callback = match scheduling_context.mode() {
633 BlockVerification => None,
634 BlockProduction => Some(|processing_result: &'_ Result<_>| {
635 if let Err(error) = processing_result {
636 Err(error.clone())?;
637 };
638
639 let RecordTransactionsSummary {
640 result,
641 starting_transaction_index,
642 ..
643 } = handler_context
644 .transaction_recorder
645 .as_ref()
646 .unwrap()
647 .record_transactions(bank.slot(), vec![transaction.to_versioned_transaction()]);
648 match result {
649 Ok(()) => Ok(starting_transaction_index),
650 Err(_) => Err(TransactionError::CommitCancelled),
651 }
652 }),
653 };
654
655 *result = execute_batch(
656 &batch_with_indexes,
657 bank,
658 handler_context.transaction_status_sender.as_ref(),
659 handler_context.replay_vote_sender.as_ref(),
660 timings,
661 handler_context.log_messages_bytes_limit,
662 &handler_context.prioritization_fee_cache,
663 pre_commit_callback,
664 );
665 sleepless_testing::at(CheckPoint::TaskHandled(index));
666 }
667}
668
669struct ExecutedTask {
670 task: Task,
671 result_with_timings: ResultWithTimings,
672}
673
674impl ExecutedTask {
675 fn new_boxed(task: Task) -> Box<Self> {
676 Box::new(Self {
677 task,
678 result_with_timings: initialized_result_with_timings(),
679 })
680 }
681}
682
683enum SubchanneledPayload<P1, P2> {
690 Payload(P1),
691 OpenSubchannel(P2),
692 CloseSubchannel,
693}
694
695type NewTaskPayload = SubchanneledPayload<Task, Box<(SchedulingContext, ResultWithTimings)>>;
696const_assert_eq!(mem::size_of::<NewTaskPayload>(), 16);
697
698mod chained_channel {
720 use super::*;
721
722 enum ChainedChannelPrivate<P, C> {
724 Payload(P),
725 ContextAndChannels(C, Receiver<ChainedChannel<P, C>>, Receiver<P>),
726 }
727
728 pub(super) struct ChainedChannel<P, C>(ChainedChannelPrivate<P, C>);
729
730 impl<P, C> ChainedChannel<P, C> {
731 fn chain_to_new_channel(
732 context: C,
733 receiver: Receiver<Self>,
734 aux_receiver: Receiver<P>,
735 ) -> Self {
736 Self(ChainedChannelPrivate::ContextAndChannels(
737 context,
738 receiver,
739 aux_receiver,
740 ))
741 }
742 }
743
744 pub(super) struct ChainedChannelSender<P, C> {
745 sender: Sender<ChainedChannel<P, C>>,
746 aux_sender: Sender<P>,
747 }
748
749 impl<P, C: Clone> ChainedChannelSender<P, C> {
750 fn new(sender: Sender<ChainedChannel<P, C>>, aux_sender: Sender<P>) -> Self {
751 Self { sender, aux_sender }
752 }
753
754 pub(super) fn send_payload(
755 &self,
756 payload: P,
757 ) -> std::result::Result<(), SendError<ChainedChannel<P, C>>> {
758 self.sender
759 .send(ChainedChannel(ChainedChannelPrivate::Payload(payload)))
760 }
761
762 pub(super) fn send_aux_payload(&self, payload: P) -> std::result::Result<(), SendError<P>> {
763 self.aux_sender.send(payload)
764 }
765
766 pub(super) fn send_chained_channel(
767 &mut self,
768 context: &C,
769 count: usize,
770 ) -> std::result::Result<(), SendError<ChainedChannel<P, C>>> {
771 let (chained_sender, chained_receiver) = crossbeam_channel::unbounded();
772 let (chained_aux_sender, chained_aux_receiver) = crossbeam_channel::unbounded();
773 for _ in 0..count {
774 self.sender.send(ChainedChannel::chain_to_new_channel(
775 context.clone(),
776 chained_receiver.clone(),
777 chained_aux_receiver.clone(),
778 ))?
779 }
780 self.sender = chained_sender;
781 self.aux_sender = chained_aux_sender;
782 Ok(())
783 }
784 }
785
786 #[derive_where(Clone)]
789 pub(super) struct ChainedChannelReceiver<P, C: Clone> {
790 receiver: Receiver<ChainedChannel<P, C>>,
791 aux_receiver: Receiver<P>,
792 context: C,
793 }
794
795 impl<P, C: Clone> ChainedChannelReceiver<P, C> {
796 fn new(
797 receiver: Receiver<ChainedChannel<P, C>>,
798 aux_receiver: Receiver<P>,
799 initial_context: C,
800 ) -> Self {
801 Self {
802 receiver,
803 aux_receiver,
804 context: initial_context,
805 }
806 }
807
808 pub(super) fn context(&self) -> &C {
809 &self.context
810 }
811
812 pub(super) fn for_select(&self) -> &Receiver<ChainedChannel<P, C>> {
813 &self.receiver
814 }
815
816 pub(super) fn aux_for_select(&self) -> &Receiver<P> {
817 &self.aux_receiver
818 }
819
820 pub(super) fn never_receive_from_aux(&mut self) {
821 self.aux_receiver = never();
822 }
823
824 pub(super) fn after_select(&mut self, message: ChainedChannel<P, C>) -> Option<P> {
825 match message.0 {
826 ChainedChannelPrivate::Payload(payload) => Some(payload),
827 ChainedChannelPrivate::ContextAndChannels(context, channel, idle_channel) => {
828 self.context = context;
829 self.receiver = channel;
830 self.aux_receiver = idle_channel;
831 None
832 }
833 }
834 }
835 }
836
837 pub(super) fn unbounded<P, C: Clone>(
838 initial_context: C,
839 ) -> (ChainedChannelSender<P, C>, ChainedChannelReceiver<P, C>) {
840 let (sender, receiver) = crossbeam_channel::unbounded();
841 let (aux_sender, aux_receiver) = crossbeam_channel::unbounded();
842 (
843 ChainedChannelSender::new(sender, aux_sender),
844 ChainedChannelReceiver::new(receiver, aux_receiver, initial_context),
845 )
846 }
847}
848
849#[derive(Default, Debug)]
856pub struct UsageQueueLoader {
857 usage_queues: DashMap<Pubkey, UsageQueue>,
858}
859
860impl UsageQueueLoader {
861 pub fn load(&self, address: Pubkey) -> UsageQueue {
862 self.usage_queues.entry(address).or_default().clone()
863 }
864
865 fn count(&self) -> usize {
866 self.usage_queues.len()
867 }
868}
869
870fn disconnected<T>() -> Receiver<T> {
874 crossbeam_channel::unbounded().1
876}
877
878#[cfg_attr(doc, aquamarine::aquamarine)]
879#[derive(Debug)]
946pub struct PooledScheduler<TH: TaskHandler> {
947 inner: PooledSchedulerInner<Self, TH>,
948 context: SchedulingContext,
949}
950
951#[derive(Debug)]
952pub struct PooledSchedulerInner<S: SpawnableScheduler<TH>, TH: TaskHandler> {
953 thread_manager: ThreadManager<S, TH>,
954 usage_queue_loader: UsageQueueLoader,
955}
956
957impl<S, TH> Drop for ThreadManager<S, TH>
958where
959 S: SpawnableScheduler<TH>,
960 TH: TaskHandler,
961{
962 fn drop(&mut self) {
963 trace!("ThreadManager::drop() is called...");
964
965 if self.are_threads_joined() {
966 return;
967 }
968 if thread::panicking() {
973 error!(
974 "ThreadManager::drop(): scheduler_id: {} skipping due to already panicking...",
975 self.scheduler_id,
976 );
977 return;
978 }
979
980 assert_matches!(self.session_result_with_timings, None);
982
983 self.new_task_sender = crossbeam_channel::unbounded().0;
986
987 self.ensure_join_threads(true);
988 assert_matches!(self.session_result_with_timings, Some((Ok(_), _)));
989 }
990}
991
992impl<S, TH> PooledSchedulerInner<S, TH>
993where
994 S: SpawnableScheduler<TH>,
995 TH: TaskHandler,
996{
997 fn is_aborted(&self) -> bool {
998 self.thread_manager.are_threads_joined()
1016 }
1017
1018 fn is_overgrown(&self) -> bool {
1019 self.usage_queue_loader.count() > self.thread_manager.pool.max_usage_queue_count
1020 }
1021}
1022
1023#[derive(Debug)]
1029struct ThreadManager<S: SpawnableScheduler<TH>, TH: TaskHandler> {
1030 scheduler_id: SchedulerId,
1031 pool: Arc<SchedulerPool<S, TH>>,
1032 new_task_sender: Sender<NewTaskPayload>,
1033 new_task_receiver: Option<Receiver<NewTaskPayload>>,
1034 session_result_sender: Sender<ResultWithTimings>,
1035 session_result_receiver: Receiver<ResultWithTimings>,
1036 session_result_with_timings: Option<ResultWithTimings>,
1037 scheduler_thread: Option<JoinHandle<()>>,
1038 handler_threads: Vec<JoinHandle<()>>,
1039}
1040
1041struct HandlerPanicked;
1042type HandlerResult = std::result::Result<Box<ExecutedTask>, HandlerPanicked>;
1043
1044impl<S: SpawnableScheduler<TH>, TH: TaskHandler> ThreadManager<S, TH> {
1045 fn new(pool: Arc<SchedulerPool<S, TH>>) -> Self {
1046 let (new_task_sender, new_task_receiver) = crossbeam_channel::unbounded();
1047 let (session_result_sender, session_result_receiver) = crossbeam_channel::unbounded();
1048
1049 Self {
1050 scheduler_id: pool.new_scheduler_id(),
1051 pool,
1052 new_task_sender,
1053 new_task_receiver: Some(new_task_receiver),
1054 session_result_sender,
1055 session_result_receiver,
1056 session_result_with_timings: None,
1057 scheduler_thread: None,
1058 handler_threads: vec![],
1059 }
1060 }
1061
1062 fn execute_task_with_handler(
1063 scheduling_context: &SchedulingContext,
1064 executed_task: &mut Box<ExecutedTask>,
1065 handler_context: &HandlerContext,
1066 ) {
1067 debug!("handling task at {:?}", thread::current());
1068 TH::handle(
1069 &mut executed_task.result_with_timings.0,
1070 &mut executed_task.result_with_timings.1,
1071 scheduling_context,
1072 &executed_task.task,
1073 handler_context,
1074 );
1075 }
1076
1077 #[must_use]
1078 fn accumulate_result_with_timings(
1079 (result, timings): &mut ResultWithTimings,
1080 executed_task: HandlerResult,
1081 ) -> Option<Box<ExecutedTask>> {
1082 let Ok(executed_task) = executed_task else {
1083 return None;
1084 };
1085 timings.accumulate(&executed_task.result_with_timings.1);
1086 match executed_task.result_with_timings.0 {
1087 Ok(()) => Some(executed_task),
1088 Err(error) => {
1089 error!("error is detected while accumulating....: {error:?}");
1090 *result = Err(error);
1091 None
1092 }
1093 }
1094 }
1095
1096 fn take_session_result_with_timings(&mut self) -> ResultWithTimings {
1097 self.session_result_with_timings.take().unwrap()
1098 }
1099
1100 fn put_session_result_with_timings(&mut self, result_with_timings: ResultWithTimings) {
1101 assert_matches!(
1102 self.session_result_with_timings
1103 .replace(result_with_timings),
1104 None
1105 );
1106 }
1107
1108 fn start_threads(
1113 &mut self,
1114 context: SchedulingContext,
1115 mut result_with_timings: ResultWithTimings,
1116 handler_context: HandlerContext,
1117 ) {
1118 let (mut runnable_task_sender, runnable_task_receiver) =
1195 chained_channel::unbounded::<Task, SchedulingContext>(context);
1196 let (finished_blocked_task_sender, finished_blocked_task_receiver) =
1201 crossbeam_channel::unbounded::<HandlerResult>();
1202 let (finished_idle_task_sender, finished_idle_task_receiver) =
1203 crossbeam_channel::unbounded::<HandlerResult>();
1204
1205 assert_matches!(self.session_result_with_timings, None);
1206
1207 let scheduler_main_loop = {
1215 let handler_count = self.pool.handler_count;
1216 let session_result_sender = self.session_result_sender.clone();
1217 let new_task_receiver = self
1221 .new_task_receiver
1222 .take()
1223 .expect("no 2nd start_threads()");
1224
1225 let mut session_ending = false;
1226
1227 move || {
1266 let (do_now, dont_now) = (&disconnected::<()>(), &never::<()>());
1267 let dummy_receiver = |trigger| {
1268 if trigger {
1269 do_now
1270 } else {
1271 dont_now
1272 }
1273 };
1274
1275 let mut state_machine = unsafe {
1276 SchedulingStateMachine::exclusively_initialize_current_thread_for_scheduling()
1277 };
1278
1279 'nonaborted_main_loop: loop {
1286 let mut is_finished = false;
1287 while !is_finished {
1288 let dummy_unblocked_task_receiver =
1293 dummy_receiver(state_machine.has_unblocked_task());
1294
1295 select_biased! {
1306 recv(finished_blocked_task_receiver) -> executed_task => {
1307 let Some(executed_task) = Self::accumulate_result_with_timings(
1308 &mut result_with_timings,
1309 executed_task.expect("alive handler"),
1310 ) else {
1311 break 'nonaborted_main_loop;
1312 };
1313 state_machine.deschedule_task(&executed_task.task);
1314 },
1315 recv(dummy_unblocked_task_receiver) -> dummy => {
1316 assert_matches!(dummy, Err(RecvError));
1317
1318 let task = state_machine
1319 .schedule_next_unblocked_task()
1320 .expect("unblocked task");
1321 runnable_task_sender.send_payload(task).unwrap();
1322 },
1323 recv(new_task_receiver) -> message => {
1324 assert!(!session_ending);
1325
1326 match message {
1327 Ok(NewTaskPayload::Payload(task)) => {
1328 sleepless_testing::at(CheckPoint::NewTask(task.task_index()));
1329 if let Some(task) = state_machine.schedule_task(task) {
1330 runnable_task_sender.send_aux_payload(task).unwrap();
1331 }
1332 }
1333 Ok(NewTaskPayload::CloseSubchannel) => {
1334 session_ending = true;
1335 }
1336 Ok(NewTaskPayload::OpenSubchannel(_context_and_result_with_timings)) =>
1337 unreachable!(),
1338 Err(RecvError) => {
1339 break 'nonaborted_main_loop;
1343 }
1344 }
1345 },
1346 recv(finished_idle_task_receiver) -> executed_task => {
1347 let Some(executed_task) = Self::accumulate_result_with_timings(
1348 &mut result_with_timings,
1349 executed_task.expect("alive handler"),
1350 ) else {
1351 break 'nonaborted_main_loop;
1352 };
1353 state_machine.deschedule_task(&executed_task.task);
1354 },
1355 };
1356
1357 is_finished = session_ending && state_machine.has_no_active_task();
1358 }
1359
1360 assert!(session_ending);
1362 session_result_sender
1364 .send(result_with_timings)
1365 .expect("always outlived receiver");
1366 state_machine.reinitialize();
1367 session_ending = false;
1368
1369 {
1370 match new_task_receiver.recv() {
1372 Ok(NewTaskPayload::OpenSubchannel(context_and_result_with_timings)) => {
1373 let (new_context, new_result_with_timings) =
1374 *context_and_result_with_timings;
1375 runnable_task_sender
1379 .send_chained_channel(&new_context, handler_count)
1380 .unwrap();
1381 result_with_timings = new_result_with_timings;
1382 }
1383 Err(_) => {
1384 result_with_timings = initialized_result_with_timings();
1387 break 'nonaborted_main_loop;
1388 }
1389 Ok(_) => unreachable!(),
1390 }
1391 }
1392 }
1393
1394 session_result_sender
1404 .send(result_with_timings)
1405 .expect("always outlived receiver");
1406
1407 drop(new_task_receiver);
1411
1412 sleepless_testing::at(CheckPoint::SchedulerThreadAborted);
1414 }
1415 };
1416
1417 let handler_main_loop = || {
1418 let mut handler_context = handler_context.clone();
1419 let mut runnable_task_receiver = runnable_task_receiver.clone();
1420 let finished_blocked_task_sender = finished_blocked_task_sender.clone();
1421 let finished_idle_task_sender = finished_idle_task_sender.clone();
1422
1423 move || {
1432 loop {
1433 let (task, sender) = select_biased! {
1434 recv(runnable_task_receiver.for_select()) -> message => {
1435 let Ok(message) = message else {
1436 break;
1437 };
1438 if let Some(task) = runnable_task_receiver.after_select(message) {
1439 (task, &finished_blocked_task_sender)
1440 } else {
1441 continue;
1442 }
1443 },
1444 recv(runnable_task_receiver.aux_for_select()) -> task => {
1445 if let Ok(task) = task {
1446 (task, &finished_idle_task_sender)
1447 } else {
1448 runnable_task_receiver.never_receive_from_aux();
1449 continue;
1450 }
1451 },
1452 recv(handler_context.banking_packet_receiver) -> banking_packet => {
1453 let Ok(banking_packet) = banking_packet else {
1456 info!("disconnected banking_packet_receiver");
1457 break;
1458 };
1459 (handler_context.banking_packet_handler)(
1460 handler_context.banking_stage_helper.as_ref().unwrap(),
1461 banking_packet
1462 );
1463 continue;
1464 },
1465 };
1466 defer! {
1467 if !thread::panicking() {
1468 return;
1469 }
1470
1471 let current_thread = thread::current();
1475 error!("handler thread is panicking: {:?}", current_thread);
1476 if sender.send(Err(HandlerPanicked)).is_ok() {
1477 info!("notified a panic from {:?}", current_thread);
1478 } else {
1479 warn!("failed to notify a panic from {:?}", current_thread);
1481 }
1482 }
1483 let mut task = ExecutedTask::new_boxed(task);
1484 Self::execute_task_with_handler(
1485 runnable_task_receiver.context(),
1486 &mut task,
1487 &handler_context,
1488 );
1489 if sender.send(Ok(task)).is_err() {
1490 warn!("handler_thread: scheduler thread aborted...");
1491 break;
1492 }
1493 }
1494 }
1495 };
1496
1497 self.scheduler_thread = Some(
1498 thread::Builder::new()
1499 .name("solScheduler".to_owned())
1500 .spawn_tracked(scheduler_main_loop)
1501 .unwrap(),
1502 );
1503
1504 self.handler_threads = (0..self.pool.handler_count)
1505 .map({
1506 |thx| {
1507 thread::Builder::new()
1508 .name(format!("solScHandler{:02}", thx))
1509 .spawn_tracked(handler_main_loop())
1510 .unwrap()
1511 }
1512 })
1513 .collect();
1514 }
1515
1516 fn send_task(&self, task: Task) -> ScheduleResult {
1517 debug!("send_task()");
1518 self.new_task_sender
1519 .send(NewTaskPayload::Payload(task))
1520 .map_err(|_| SchedulerAborted)
1521 }
1522
1523 fn ensure_join_threads(&mut self, should_receive_session_result: bool) {
1524 trace!("ensure_join_threads() is called");
1525
1526 fn join_with_panic_message(join_handle: JoinHandle<()>) -> thread::Result<()> {
1527 let thread = join_handle.thread().clone();
1528 join_handle.join().inspect_err(|e| {
1529 let panic_message = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
1535 (Some(&s), _) => s,
1536 (_, Some(s)) => s,
1537 (None, None) => "<No panic info>",
1538 };
1539 panic!("{} (From: {:?})", panic_message, thread);
1540 })
1541 }
1542
1543 if let Some(scheduler_thread) = self.scheduler_thread.take() {
1544 for thread in self.handler_threads.drain(..) {
1545 debug!("joining...: {:?}", thread);
1546 () = join_with_panic_message(thread).unwrap();
1547 }
1548 () = join_with_panic_message(scheduler_thread).unwrap();
1549
1550 if should_receive_session_result {
1551 let result_with_timings = self.session_result_receiver.recv().unwrap();
1552 debug!("ensure_join_threads(): err: {:?}", result_with_timings.0);
1553 self.put_session_result_with_timings(result_with_timings);
1554 }
1555 } else {
1556 warn!("ensure_join_threads(): skipping; already joined...");
1557 };
1558 }
1559
1560 fn ensure_join_threads_after_abort(
1561 &mut self,
1562 should_receive_aborted_session_result: bool,
1563 ) -> TransactionError {
1564 self.ensure_join_threads(should_receive_aborted_session_result);
1565 self.session_result_with_timings
1566 .as_mut()
1567 .unwrap()
1568 .0
1569 .clone()
1570 .unwrap_err()
1571 }
1572
1573 fn are_threads_joined(&self) -> bool {
1574 if self.scheduler_thread.is_none() {
1575 assert!(self.handler_threads.is_empty());
1578 true
1579 } else {
1580 false
1581 }
1582 }
1583
1584 fn end_session(&mut self) {
1585 if self.are_threads_joined() {
1586 assert!(self.session_result_with_timings.is_some());
1587 debug!("end_session(): skipping; already joined the aborted threads..");
1588 return;
1589 } else if self.session_result_with_timings.is_some() {
1590 debug!("end_session(): skipping; already result resides within thread manager..");
1591 return;
1592 }
1593 debug!(
1594 "end_session(): will end session at {:?}...",
1595 thread::current(),
1596 );
1597
1598 let mut abort_detected = self
1599 .new_task_sender
1600 .send(NewTaskPayload::CloseSubchannel)
1601 .is_err();
1602
1603 if abort_detected {
1604 self.ensure_join_threads_after_abort(true);
1605 return;
1606 }
1607
1608 let result_with_timings = self.session_result_receiver.recv().unwrap();
1611 abort_detected = result_with_timings.0.is_err();
1612 self.put_session_result_with_timings(result_with_timings);
1613 if abort_detected {
1614 self.ensure_join_threads_after_abort(false);
1615 }
1616 debug!("end_session(): ended session at {:?}...", thread::current());
1617 }
1618
1619 fn start_session(
1620 &mut self,
1621 context: SchedulingContext,
1622 result_with_timings: ResultWithTimings,
1623 ) {
1624 assert!(!self.are_threads_joined());
1625 assert_matches!(self.session_result_with_timings, None);
1626 self.new_task_sender
1627 .send(NewTaskPayload::OpenSubchannel(Box::new((
1628 context,
1629 result_with_timings,
1630 ))))
1631 .expect("no new session after aborted");
1632 }
1633}
1634
1635pub trait SchedulerInner {
1636 fn id(&self) -> SchedulerId;
1637 fn is_trashed(&self) -> bool;
1638}
1639
1640pub trait SpawnableScheduler<TH: TaskHandler>: InstalledScheduler {
1641 type Inner: SchedulerInner + Debug + Send + Sync;
1642
1643 fn into_inner(self) -> (ResultWithTimings, Self::Inner);
1644
1645 fn from_inner(
1646 inner: Self::Inner,
1647 context: SchedulingContext,
1648 result_with_timings: ResultWithTimings,
1649 ) -> Self;
1650
1651 fn spawn(
1652 pool: Arc<SchedulerPool<Self, TH>>,
1653 context: SchedulingContext,
1654 result_with_timings: ResultWithTimings,
1655 ) -> Self
1656 where
1657 Self: Sized;
1658}
1659
1660impl<TH: TaskHandler> SpawnableScheduler<TH> for PooledScheduler<TH> {
1661 type Inner = PooledSchedulerInner<Self, TH>;
1662
1663 fn into_inner(mut self) -> (ResultWithTimings, Self::Inner) {
1664 let result_with_timings = {
1665 let manager = &mut self.inner.thread_manager;
1666 manager.end_session();
1667 manager.take_session_result_with_timings()
1668 };
1669 (result_with_timings, self.inner)
1670 }
1671
1672 fn from_inner(
1673 mut inner: Self::Inner,
1674 context: SchedulingContext,
1675 result_with_timings: ResultWithTimings,
1676 ) -> Self {
1677 inner
1678 .thread_manager
1679 .start_session(context.clone(), result_with_timings);
1680 Self { inner, context }
1681 }
1682
1683 fn spawn(
1684 pool: Arc<SchedulerPool<Self, TH>>,
1685 context: SchedulingContext,
1686 result_with_timings: ResultWithTimings,
1687 ) -> Self {
1688 let mut inner = Self::Inner {
1689 thread_manager: ThreadManager::new(pool.clone()),
1690 usage_queue_loader: UsageQueueLoader::default(),
1691 };
1692 inner.thread_manager.start_threads(
1693 context.clone(),
1694 result_with_timings,
1695 pool.create_handler_context(context.mode(), &inner.thread_manager.new_task_sender),
1696 );
1697 Self { inner, context }
1698 }
1699}
1700
1701impl<TH: TaskHandler> InstalledScheduler for PooledScheduler<TH> {
1702 fn id(&self) -> SchedulerId {
1703 self.inner.id()
1704 }
1705
1706 fn context(&self) -> &SchedulingContext {
1707 &self.context
1708 }
1709
1710 fn schedule_execution(
1711 &self,
1712 transaction: RuntimeTransaction<SanitizedTransaction>,
1713 index: usize,
1714 ) -> ScheduleResult {
1715 let task = SchedulingStateMachine::create_task(transaction, index, &mut |pubkey| {
1716 self.inner.usage_queue_loader.load(pubkey)
1717 });
1718 self.inner.thread_manager.send_task(task)
1719 }
1720
1721 fn recover_error_after_abort(&mut self) -> TransactionError {
1722 self.inner
1723 .thread_manager
1724 .ensure_join_threads_after_abort(true)
1725 }
1726
1727 fn wait_for_termination(
1728 self: Box<Self>,
1729 _is_dropped: bool,
1730 ) -> (ResultWithTimings, UninstalledSchedulerBox) {
1731 let (result_with_timings, uninstalled_scheduler) = self.into_inner();
1732 (result_with_timings, Box::new(uninstalled_scheduler))
1733 }
1734
1735 fn pause_for_recent_blockhash(&mut self) {
1736 self.inner.thread_manager.end_session();
1737 }
1738}
1739
1740impl<S, TH> SchedulerInner for PooledSchedulerInner<S, TH>
1741where
1742 S: SpawnableScheduler<TH>,
1743 TH: TaskHandler,
1744{
1745 fn id(&self) -> SchedulerId {
1746 self.thread_manager.scheduler_id
1747 }
1748
1749 fn is_trashed(&self) -> bool {
1750 self.is_aborted() || self.is_overgrown()
1751 }
1752}
1753
1754impl<S, TH> UninstalledScheduler for PooledSchedulerInner<S, TH>
1755where
1756 S: SpawnableScheduler<TH, Inner = Self>,
1757 TH: TaskHandler,
1758{
1759 fn return_to_pool(self: Box<Self>) {
1760 self.thread_manager.pool.clone().return_scheduler(*self);
1761 }
1762}
1763
1764#[cfg(test)]
1765mod tests {
1766 use {
1767 super::*,
1768 crate::sleepless_testing,
1769 assert_matches::assert_matches,
1770 clone_solana_clock::{Slot, MAX_PROCESSING_AGE},
1771 clone_solana_hash::Hash,
1772 clone_solana_keypair::Keypair,
1773 clone_solana_ledger::{
1774 blockstore::Blockstore,
1775 blockstore_processor::{TransactionStatusBatch, TransactionStatusMessage},
1776 create_new_tmp_ledger_auto_delete,
1777 leader_schedule_cache::LeaderScheduleCache,
1778 },
1779 clone_solana_poh::poh_recorder::create_test_recorder_with_index_tracking,
1780 clone_solana_pubkey::Pubkey,
1781 clone_solana_runtime::{
1782 bank::Bank,
1783 bank_forks::BankForks,
1784 genesis_utils::{create_genesis_config, GenesisConfigInfo},
1785 installed_scheduler_pool::{BankWithScheduler, SchedulingContext},
1786 prioritization_fee_cache::PrioritizationFeeCache,
1787 },
1788 clone_solana_system_transaction as system_transaction,
1789 clone_solana_timings::ExecuteTimingType,
1790 clone_solana_transaction::sanitized::SanitizedTransaction,
1791 clone_solana_transaction_error::TransactionError,
1792 std::{
1793 sync::{atomic::Ordering, Arc, RwLock},
1794 thread::JoinHandle,
1795 },
1796 test_case::test_matrix,
1797 };
1798
1799 #[derive(Debug)]
1800 enum TestCheckPoint {
1801 BeforeNewTask,
1802 AfterTaskHandled,
1803 AfterSchedulerThreadAborted,
1804 BeforeIdleSchedulerCleaned,
1805 AfterIdleSchedulerCleaned,
1806 BeforeTrashedSchedulerCleaned,
1807 AfterTrashedSchedulerCleaned,
1808 BeforeTimeoutListenerTriggered,
1809 AfterTimeoutListenerTriggered,
1810 BeforeThreadManagerDrop,
1811 BeforeEndSession,
1812 }
1813
1814 #[test]
1815 fn test_scheduler_pool_new() {
1816 clone_solana_logger::setup();
1817
1818 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
1819 let pool =
1820 DefaultSchedulerPool::new_dyn(None, None, None, None, ignored_prioritization_fee_cache);
1821
1822 assert_eq!((Arc::strong_count(&pool), Arc::weak_count(&pool)), (1, 2));
1826 let debug = format!("{pool:#?}");
1827 assert!(!debug.is_empty());
1828 }
1829
1830 #[test]
1831 fn test_scheduler_spawn() {
1832 clone_solana_logger::setup();
1833
1834 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
1835 let pool =
1836 DefaultSchedulerPool::new_dyn(None, None, None, None, ignored_prioritization_fee_cache);
1837 let bank = Arc::new(Bank::default_for_tests());
1838 let context = SchedulingContext::new(bank);
1839 let scheduler = pool.take_scheduler(context);
1840
1841 let debug = format!("{scheduler:#?}");
1842 assert!(!debug.is_empty());
1843 }
1844
1845 const SHORTENED_POOL_CLEANER_INTERVAL: Duration = Duration::from_millis(1);
1846 const SHORTENED_MAX_POOLING_DURATION: Duration = Duration::from_millis(100);
1847
1848 #[test]
1849 fn test_scheduler_drop_idle() {
1850 clone_solana_logger::setup();
1851
1852 let _progress = sleepless_testing::setup(&[
1853 &TestCheckPoint::BeforeIdleSchedulerCleaned,
1854 &CheckPoint::IdleSchedulerCleaned(0),
1855 &CheckPoint::IdleSchedulerCleaned(1),
1856 &TestCheckPoint::AfterIdleSchedulerCleaned,
1857 ]);
1858
1859 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
1860 let pool_raw = DefaultSchedulerPool::do_new(
1861 None,
1862 None,
1863 None,
1864 None,
1865 ignored_prioritization_fee_cache,
1866 SHORTENED_POOL_CLEANER_INTERVAL,
1867 SHORTENED_MAX_POOLING_DURATION,
1868 DEFAULT_MAX_USAGE_QUEUE_COUNT,
1869 DEFAULT_TIMEOUT_DURATION,
1870 );
1871 let pool = pool_raw.clone();
1872 let bank = Arc::new(Bank::default_for_tests());
1873 let context1 = SchedulingContext::new(bank);
1874 let context2 = context1.clone();
1875
1876 let old_scheduler = pool.do_take_scheduler(context1);
1877 let new_scheduler = pool.do_take_scheduler(context2);
1878 let new_scheduler_id = new_scheduler.id();
1879 Box::new(old_scheduler.into_inner().1).return_to_pool();
1880
1881 sleep(SHORTENED_MAX_POOLING_DURATION * 10);
1883 Box::new(new_scheduler.into_inner().1).return_to_pool();
1884
1885 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 2);
1887 sleepless_testing::at(TestCheckPoint::BeforeIdleSchedulerCleaned);
1888
1889 sleepless_testing::at(&TestCheckPoint::AfterIdleSchedulerCleaned);
1891
1892 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
1899 assert_eq!(
1900 pool_raw
1901 .scheduler_inners
1902 .lock()
1903 .unwrap()
1904 .first()
1905 .as_ref()
1906 .map(|(inner, _pooled_at)| inner.id())
1907 .unwrap(),
1908 new_scheduler_id
1909 );
1910 }
1911
1912 #[test]
1913 fn test_scheduler_drop_overgrown() {
1914 clone_solana_logger::setup();
1915
1916 let _progress = sleepless_testing::setup(&[
1917 &TestCheckPoint::BeforeTrashedSchedulerCleaned,
1918 &CheckPoint::TrashedSchedulerCleaned(0),
1919 &CheckPoint::TrashedSchedulerCleaned(1),
1920 &TestCheckPoint::AfterTrashedSchedulerCleaned,
1921 ]);
1922
1923 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
1924 const REDUCED_MAX_USAGE_QUEUE_COUNT: usize = 1;
1925 let pool_raw = DefaultSchedulerPool::do_new(
1926 None,
1927 None,
1928 None,
1929 None,
1930 ignored_prioritization_fee_cache,
1931 SHORTENED_POOL_CLEANER_INTERVAL,
1932 DEFAULT_MAX_POOLING_DURATION,
1933 REDUCED_MAX_USAGE_QUEUE_COUNT,
1934 DEFAULT_TIMEOUT_DURATION,
1935 );
1936 let pool = pool_raw.clone();
1937 let bank = Arc::new(Bank::default_for_tests());
1938 let context1 = SchedulingContext::new(bank);
1939 let context2 = context1.clone();
1940
1941 let small_scheduler = pool.do_take_scheduler(context1);
1942 let small_scheduler_id = small_scheduler.id();
1943 for _ in 0..REDUCED_MAX_USAGE_QUEUE_COUNT {
1944 small_scheduler
1945 .inner
1946 .usage_queue_loader
1947 .load(Pubkey::new_unique());
1948 }
1949 let big_scheduler = pool.do_take_scheduler(context2);
1950 for _ in 0..REDUCED_MAX_USAGE_QUEUE_COUNT + 1 {
1951 big_scheduler
1952 .inner
1953 .usage_queue_loader
1954 .load(Pubkey::new_unique());
1955 }
1956
1957 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0);
1958 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
1959 Box::new(small_scheduler.into_inner().1).return_to_pool();
1960 Box::new(big_scheduler.into_inner().1).return_to_pool();
1961
1962 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
1964 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 1);
1965 sleepless_testing::at(TestCheckPoint::BeforeTrashedSchedulerCleaned);
1966
1967 sleepless_testing::at(&TestCheckPoint::AfterTrashedSchedulerCleaned);
1969 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
1970 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
1971 assert_eq!(
1972 pool_raw
1973 .scheduler_inners
1974 .lock()
1975 .unwrap()
1976 .first()
1977 .as_ref()
1978 .map(|(inner, _pooled_at)| inner.id())
1979 .unwrap(),
1980 small_scheduler_id
1981 );
1982 }
1983
1984 const SHORTENED_TIMEOUT_DURATION: Duration = Duration::from_millis(1);
1985
1986 #[test]
1987 fn test_scheduler_drop_stale() {
1988 clone_solana_logger::setup();
1989
1990 let _progress = sleepless_testing::setup(&[
1991 &TestCheckPoint::BeforeTimeoutListenerTriggered,
1992 &CheckPoint::TimeoutListenerTriggered(0),
1993 &CheckPoint::TimeoutListenerTriggered(1),
1994 &TestCheckPoint::AfterTimeoutListenerTriggered,
1995 &CheckPoint::IdleSchedulerCleaned(1),
1996 &TestCheckPoint::AfterIdleSchedulerCleaned,
1997 ]);
1998
1999 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2000 let pool_raw = DefaultSchedulerPool::do_new(
2001 None,
2002 None,
2003 None,
2004 None,
2005 ignored_prioritization_fee_cache,
2006 SHORTENED_POOL_CLEANER_INTERVAL,
2007 SHORTENED_MAX_POOLING_DURATION,
2008 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2009 SHORTENED_TIMEOUT_DURATION,
2010 );
2011 let pool = pool_raw.clone();
2012 let bank = Arc::new(Bank::default_for_tests());
2013 let context = SchedulingContext::new(bank.clone());
2014 let scheduler = pool.take_scheduler(context);
2015 let bank = BankWithScheduler::new(bank, Some(scheduler));
2016 pool.register_timeout_listener(bank.create_timeout_listener());
2017 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0);
2018 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2019 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2020
2021 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2022 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
2023 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2024 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
2025
2026 sleepless_testing::at(&TestCheckPoint::AfterIdleSchedulerCleaned);
2028 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0);
2029 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2030 }
2031
2032 #[test]
2033 fn test_scheduler_active_after_stale() {
2034 clone_solana_logger::setup();
2035
2036 let _progress = sleepless_testing::setup(&[
2037 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2038 &CheckPoint::TimeoutListenerTriggered(0),
2039 &CheckPoint::TimeoutListenerTriggered(1),
2040 &TestCheckPoint::AfterTimeoutListenerTriggered,
2041 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2042 &CheckPoint::TimeoutListenerTriggered(0),
2043 &CheckPoint::TimeoutListenerTriggered(1),
2044 &TestCheckPoint::AfterTimeoutListenerTriggered,
2045 ]);
2046
2047 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2048 let pool_raw = SchedulerPool::<PooledScheduler<ExecuteTimingCounter>, _>::do_new(
2049 None,
2050 None,
2051 None,
2052 None,
2053 ignored_prioritization_fee_cache,
2054 SHORTENED_POOL_CLEANER_INTERVAL,
2055 DEFAULT_MAX_POOLING_DURATION,
2056 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2057 SHORTENED_TIMEOUT_DURATION,
2058 );
2059
2060 #[derive(Debug)]
2061 struct ExecuteTimingCounter;
2062 impl TaskHandler for ExecuteTimingCounter {
2063 fn handle(
2064 _result: &mut Result<()>,
2065 timings: &mut ExecuteTimings,
2066 _bank: &SchedulingContext,
2067 _task: &Task,
2068 _handler_context: &HandlerContext,
2069 ) {
2070 timings.metrics[ExecuteTimingType::CheckUs] += 123;
2071 }
2072 }
2073 let pool = pool_raw.clone();
2074
2075 let GenesisConfigInfo {
2076 genesis_config,
2077 mint_keypair,
2078 ..
2079 } = create_genesis_config(10_000);
2080 let bank = Bank::new_for_tests(&genesis_config);
2081 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2082
2083 let context = SchedulingContext::new(bank.clone());
2084
2085 let scheduler = pool.take_scheduler(context);
2086 let bank = BankWithScheduler::new(bank, Some(scheduler));
2087 pool.register_timeout_listener(bank.create_timeout_listener());
2088
2089 let tx_before_stale =
2090 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2091 &mint_keypair,
2092 &clone_solana_pubkey::new_rand(),
2093 2,
2094 genesis_config.hash(),
2095 ));
2096 bank.schedule_transaction_executions([(tx_before_stale, 0)].into_iter())
2097 .unwrap();
2098 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2099
2100 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2101 let tx_after_stale =
2102 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2103 &mint_keypair,
2104 &clone_solana_pubkey::new_rand(),
2105 2,
2106 genesis_config.hash(),
2107 ));
2108 bank.schedule_transaction_executions([(tx_after_stale, 1)].into_iter())
2109 .unwrap();
2110
2111 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2114 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2115
2116 let (result, timings) = bank.wait_for_completed_scheduler().unwrap();
2117 assert_matches!(result, Ok(()));
2118 assert_eq!(timings.metrics[ExecuteTimingType::CheckUs].0, 246);
2120 }
2121
2122 #[test]
2123 fn test_scheduler_pause_after_stale() {
2124 clone_solana_logger::setup();
2125
2126 let _progress = sleepless_testing::setup(&[
2127 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2128 &CheckPoint::TimeoutListenerTriggered(0),
2129 &CheckPoint::TimeoutListenerTriggered(1),
2130 &TestCheckPoint::AfterTimeoutListenerTriggered,
2131 ]);
2132
2133 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2134 let pool_raw = DefaultSchedulerPool::do_new(
2135 None,
2136 None,
2137 None,
2138 None,
2139 ignored_prioritization_fee_cache,
2140 SHORTENED_POOL_CLEANER_INTERVAL,
2141 DEFAULT_MAX_POOLING_DURATION,
2142 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2143 SHORTENED_TIMEOUT_DURATION,
2144 );
2145 let pool = pool_raw.clone();
2146
2147 let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
2148 let bank = Bank::new_for_tests(&genesis_config);
2149 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2150
2151 let context = SchedulingContext::new(bank.clone());
2152
2153 let scheduler = pool.take_scheduler(context);
2154 let bank = BankWithScheduler::new(bank, Some(scheduler));
2155 pool.register_timeout_listener(bank.create_timeout_listener());
2156
2157 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2158 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2159
2160 bank.fill_bank_with_ticks_for_tests();
2163 let (result, _timings) = bank.wait_for_completed_scheduler().unwrap();
2164 assert_matches!(result, Ok(()));
2165 }
2166
2167 #[test]
2168 fn test_scheduler_remain_stale_after_error() {
2169 clone_solana_logger::setup();
2170
2171 let _progress = sleepless_testing::setup(&[
2172 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2173 &CheckPoint::TimeoutListenerTriggered(0),
2174 &CheckPoint::SchedulerThreadAborted,
2175 &TestCheckPoint::AfterSchedulerThreadAborted,
2176 &CheckPoint::TimeoutListenerTriggered(1),
2177 &TestCheckPoint::AfterTimeoutListenerTriggered,
2178 ]);
2179
2180 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2181 let pool_raw = SchedulerPool::<PooledScheduler<FaultyHandler>, _>::do_new(
2182 None,
2183 None,
2184 None,
2185 None,
2186 ignored_prioritization_fee_cache,
2187 SHORTENED_POOL_CLEANER_INTERVAL,
2188 DEFAULT_MAX_POOLING_DURATION,
2189 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2190 SHORTENED_TIMEOUT_DURATION,
2191 );
2192
2193 let pool = pool_raw.clone();
2194
2195 let GenesisConfigInfo {
2196 genesis_config,
2197 mint_keypair,
2198 ..
2199 } = create_genesis_config(10_000);
2200 let bank = Bank::new_for_tests(&genesis_config);
2201 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2202
2203 let context = SchedulingContext::new(bank.clone());
2204
2205 let scheduler = pool.take_scheduler(context);
2206 let bank = BankWithScheduler::new(bank, Some(scheduler));
2207 pool.register_timeout_listener(bank.create_timeout_listener());
2208
2209 let tx_before_stale =
2210 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2211 &mint_keypair,
2212 &clone_solana_pubkey::new_rand(),
2213 2,
2214 genesis_config.hash(),
2215 ));
2216 bank.schedule_transaction_executions([(tx_before_stale, 0)].into_iter())
2217 .unwrap();
2218 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2219 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2220
2221 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2222 let tx_after_stale =
2223 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2224 &mint_keypair,
2225 &clone_solana_pubkey::new_rand(),
2226 2,
2227 genesis_config.hash(),
2228 ));
2229 let result = bank.schedule_transaction_executions([(tx_after_stale, 1)].into_iter());
2230 assert_matches!(result, Err(TransactionError::AccountNotFound));
2231
2232 let (result, _timings) = bank.wait_for_completed_scheduler().unwrap();
2233 assert_matches!(result, Err(TransactionError::AccountNotFound));
2234 }
2235
2236 enum AbortCase {
2237 Unhandled,
2238 UnhandledWhilePanicking,
2239 Handled,
2240 }
2241
2242 #[derive(Debug)]
2243 struct FaultyHandler;
2244 impl TaskHandler for FaultyHandler {
2245 fn handle(
2246 result: &mut Result<()>,
2247 _timings: &mut ExecuteTimings,
2248 _bank: &SchedulingContext,
2249 _task: &Task,
2250 _handler_context: &HandlerContext,
2251 ) {
2252 *result = Err(TransactionError::AccountNotFound);
2253 }
2254 }
2255
2256 fn do_test_scheduler_drop_abort(abort_case: AbortCase) {
2257 clone_solana_logger::setup();
2258
2259 let _progress = sleepless_testing::setup(match abort_case {
2260 AbortCase::Unhandled => &[
2261 &CheckPoint::SchedulerThreadAborted,
2262 &TestCheckPoint::AfterSchedulerThreadAborted,
2263 ],
2264 _ => &[],
2265 });
2266
2267 let GenesisConfigInfo {
2268 genesis_config,
2269 mint_keypair,
2270 ..
2271 } = create_genesis_config(10_000);
2272
2273 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2274 &mint_keypair,
2275 &clone_solana_pubkey::new_rand(),
2276 2,
2277 genesis_config.hash(),
2278 ));
2279
2280 let bank = Bank::new_for_tests(&genesis_config);
2281 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2282 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2283 let pool = SchedulerPool::<PooledScheduler<FaultyHandler>, _>::new(
2284 None,
2285 None,
2286 None,
2287 None,
2288 ignored_prioritization_fee_cache,
2289 );
2290 let context = SchedulingContext::new(bank.clone());
2291 let scheduler = pool.do_take_scheduler(context);
2292 scheduler.schedule_execution(tx, 0).unwrap();
2293
2294 match abort_case {
2295 AbortCase::Unhandled => {
2296 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2297 drop::<PooledScheduler<_>>(scheduler);
2301 }
2302 AbortCase::UnhandledWhilePanicking => {
2303 panic!("ThreadManager::drop() should be skipped...");
2305 }
2306 AbortCase::Handled => {
2307 let ((result, _), mut scheduler_inner) = scheduler.into_inner();
2309 assert_matches!(result, Err(TransactionError::AccountNotFound));
2310
2311 let dummy_flag = true; scheduler_inner
2314 .thread_manager
2315 .ensure_join_threads(dummy_flag);
2316
2317 drop::<PooledSchedulerInner<_, _>>(scheduler_inner);
2318 }
2319 }
2320 }
2321
2322 #[test]
2323 #[should_panic(expected = "does not match `Some((Ok(_), _))")]
2324 fn test_scheduler_drop_abort_unhandled() {
2325 do_test_scheduler_drop_abort(AbortCase::Unhandled);
2326 }
2327
2328 #[test]
2329 #[should_panic(expected = "ThreadManager::drop() should be skipped...")]
2330 fn test_scheduler_drop_abort_unhandled_while_panicking() {
2331 do_test_scheduler_drop_abort(AbortCase::UnhandledWhilePanicking);
2332 }
2333
2334 #[test]
2335 fn test_scheduler_drop_abort_handled() {
2336 do_test_scheduler_drop_abort(AbortCase::Handled);
2337 }
2338
2339 #[test]
2340 fn test_scheduler_drop_short_circuiting() {
2341 clone_solana_logger::setup();
2342
2343 let _progress = sleepless_testing::setup(&[
2344 &TestCheckPoint::BeforeThreadManagerDrop,
2345 &CheckPoint::NewTask(0),
2346 &CheckPoint::SchedulerThreadAborted,
2347 &TestCheckPoint::AfterSchedulerThreadAborted,
2348 ]);
2349
2350 static TASK_COUNT: Mutex<usize> = Mutex::new(0);
2351
2352 #[derive(Debug)]
2353 struct CountingHandler;
2354 impl TaskHandler for CountingHandler {
2355 fn handle(
2356 _result: &mut Result<()>,
2357 _timings: &mut ExecuteTimings,
2358 _bank: &SchedulingContext,
2359 _task: &Task,
2360 _handler_context: &HandlerContext,
2361 ) {
2362 *TASK_COUNT.lock().unwrap() += 1;
2363 }
2364 }
2365
2366 let GenesisConfigInfo {
2367 genesis_config,
2368 mint_keypair,
2369 ..
2370 } = create_genesis_config(10_000);
2371
2372 let bank = Bank::new_for_tests(&genesis_config);
2373 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2374 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2375 let pool = SchedulerPool::<PooledScheduler<CountingHandler>, _>::new(
2376 None,
2377 None,
2378 None,
2379 None,
2380 ignored_prioritization_fee_cache,
2381 );
2382 let context = SchedulingContext::new(bank.clone());
2383 let scheduler = pool.do_take_scheduler(context);
2384
2385 const MAX_TASK_COUNT: usize = 100;
2391
2392 for i in 0..MAX_TASK_COUNT {
2393 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2394 &mint_keypair,
2395 &clone_solana_pubkey::new_rand(),
2396 2,
2397 genesis_config.hash(),
2398 ));
2399 scheduler.schedule_execution(tx, i).unwrap();
2400 }
2401
2402 sleepless_testing::at(TestCheckPoint::BeforeThreadManagerDrop);
2404 drop::<PooledScheduler<_>>(scheduler);
2405 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2406 assert!(*TASK_COUNT.lock().unwrap() < MAX_TASK_COUNT);
2408 }
2409
2410 #[test]
2411 fn test_scheduler_pool_filo() {
2412 clone_solana_logger::setup();
2413
2414 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2415 let pool =
2416 DefaultSchedulerPool::new(None, None, None, None, ignored_prioritization_fee_cache);
2417 let bank = Arc::new(Bank::default_for_tests());
2418 let context = &SchedulingContext::new(bank);
2419
2420 let scheduler1 = pool.do_take_scheduler(context.clone());
2421 let scheduler_id1 = scheduler1.id();
2422 let scheduler2 = pool.do_take_scheduler(context.clone());
2423 let scheduler_id2 = scheduler2.id();
2424 assert_ne!(scheduler_id1, scheduler_id2);
2425
2426 let (result_with_timings, scheduler1) = scheduler1.into_inner();
2427 assert_matches!(result_with_timings, (Ok(()), _));
2428 pool.return_scheduler(scheduler1);
2429 let (result_with_timings, scheduler2) = scheduler2.into_inner();
2430 assert_matches!(result_with_timings, (Ok(()), _));
2431 pool.return_scheduler(scheduler2);
2432
2433 let scheduler3 = pool.do_take_scheduler(context.clone());
2434 assert_eq!(scheduler_id2, scheduler3.id());
2435 let scheduler4 = pool.do_take_scheduler(context.clone());
2436 assert_eq!(scheduler_id1, scheduler4.id());
2437 }
2438
2439 #[test]
2440 fn test_scheduler_pool_context_drop_unless_reinitialized() {
2441 clone_solana_logger::setup();
2442
2443 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2444 let pool =
2445 DefaultSchedulerPool::new(None, None, None, None, ignored_prioritization_fee_cache);
2446 let bank = Arc::new(Bank::default_for_tests());
2447 let context = &SchedulingContext::new(bank);
2448 let mut scheduler = pool.do_take_scheduler(context.clone());
2449
2450 scheduler.pause_for_recent_blockhash();
2452 assert_matches!(
2453 Box::new(scheduler).wait_for_termination(false),
2454 ((Ok(()), _), _)
2455 );
2456 }
2457
2458 #[test]
2459 fn test_scheduler_pool_context_replace() {
2460 clone_solana_logger::setup();
2461
2462 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2463 let pool =
2464 DefaultSchedulerPool::new(None, None, None, None, ignored_prioritization_fee_cache);
2465 let old_bank = &Arc::new(Bank::default_for_tests());
2466 let new_bank = &Arc::new(Bank::default_for_tests());
2467 assert!(!Arc::ptr_eq(old_bank, new_bank));
2468
2469 let old_context = &SchedulingContext::new(old_bank.clone());
2470 let new_context = &SchedulingContext::new(new_bank.clone());
2471
2472 let scheduler = pool.do_take_scheduler(old_context.clone());
2473 let scheduler_id = scheduler.id();
2474 pool.return_scheduler(scheduler.into_inner().1);
2475
2476 let scheduler = pool.take_scheduler(new_context.clone());
2477 assert_eq!(scheduler_id, scheduler.id());
2478 assert!(Arc::ptr_eq(scheduler.context().bank(), new_bank));
2479 }
2480
2481 #[test]
2482 fn test_scheduler_pool_install_into_bank_forks() {
2483 clone_solana_logger::setup();
2484
2485 let bank = Bank::default_for_tests();
2486 let bank_forks = BankForks::new_rw_arc(bank);
2487 let mut bank_forks = bank_forks.write().unwrap();
2488 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2489 let pool =
2490 DefaultSchedulerPool::new_dyn(None, None, None, None, ignored_prioritization_fee_cache);
2491 bank_forks.install_scheduler_pool(pool);
2492 }
2493
2494 #[test]
2495 fn test_scheduler_install_into_bank() {
2496 clone_solana_logger::setup();
2497
2498 let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
2499 let bank = Arc::new(Bank::new_for_tests(&genesis_config));
2500 let child_bank = Bank::new_from_parent(bank, &Pubkey::default(), 1);
2501
2502 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2503 let pool =
2504 DefaultSchedulerPool::new_dyn(None, None, None, None, ignored_prioritization_fee_cache);
2505
2506 let bank = Bank::default_for_tests();
2507 let bank_forks = BankForks::new_rw_arc(bank);
2508 let mut bank_forks = bank_forks.write().unwrap();
2509
2510 assert!(!bank_forks
2513 .working_bank_with_scheduler()
2514 .has_installed_scheduler());
2515 bank_forks.install_scheduler_pool(pool);
2516 assert!(!bank_forks
2517 .working_bank_with_scheduler()
2518 .has_installed_scheduler());
2519
2520 let mut child_bank = bank_forks.insert(child_bank);
2521 assert!(child_bank.has_installed_scheduler());
2522 bank_forks.remove(child_bank.slot());
2523 child_bank.drop_scheduler();
2524 assert!(!child_bank.has_installed_scheduler());
2525 }
2526
2527 fn setup_dummy_fork_graph(bank: Bank) -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
2528 let slot = bank.slot();
2529 let bank_fork = BankForks::new_rw_arc(bank);
2530 let bank = bank_fork.read().unwrap().get(slot).unwrap();
2531 bank.set_fork_graph_in_program_cache(Arc::downgrade(&bank_fork));
2532 (bank, bank_fork)
2533 }
2534
2535 #[test]
2536 fn test_scheduler_schedule_execution_success() {
2537 clone_solana_logger::setup();
2538
2539 let GenesisConfigInfo {
2540 genesis_config,
2541 mint_keypair,
2542 ..
2543 } = create_genesis_config(10_000);
2544 let tx0 = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2545 &mint_keypair,
2546 &clone_solana_pubkey::new_rand(),
2547 2,
2548 genesis_config.hash(),
2549 ));
2550 let bank = Bank::new_for_tests(&genesis_config);
2551 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2552 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2553 let pool =
2554 DefaultSchedulerPool::new_dyn(None, None, None, None, ignored_prioritization_fee_cache);
2555 let context = SchedulingContext::new(bank.clone());
2556
2557 assert_eq!(bank.transaction_count(), 0);
2558 let scheduler = pool.take_scheduler(context);
2559 scheduler.schedule_execution(tx0, 0).unwrap();
2560 let bank = BankWithScheduler::new(bank, Some(scheduler));
2561 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
2562 assert_eq!(bank.transaction_count(), 1);
2563 }
2564
2565 fn do_test_scheduler_schedule_execution_failure(extra_tx_after_failure: bool) {
2566 clone_solana_logger::setup();
2567
2568 let _progress = sleepless_testing::setup(&[
2569 &CheckPoint::TaskHandled(0),
2570 &TestCheckPoint::AfterTaskHandled,
2571 &CheckPoint::SchedulerThreadAborted,
2572 &TestCheckPoint::AfterSchedulerThreadAborted,
2573 &TestCheckPoint::BeforeTrashedSchedulerCleaned,
2574 &CheckPoint::TrashedSchedulerCleaned(0),
2575 &CheckPoint::TrashedSchedulerCleaned(1),
2576 &TestCheckPoint::AfterTrashedSchedulerCleaned,
2577 ]);
2578
2579 let GenesisConfigInfo {
2580 genesis_config,
2581 mint_keypair,
2582 ..
2583 } = create_genesis_config(10_000);
2584 let bank = Bank::new_for_tests(&genesis_config);
2585 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2586
2587 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2588 let pool_raw = DefaultSchedulerPool::do_new(
2589 None,
2590 None,
2591 None,
2592 None,
2593 ignored_prioritization_fee_cache,
2594 SHORTENED_POOL_CLEANER_INTERVAL,
2595 DEFAULT_MAX_POOLING_DURATION,
2596 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2597 DEFAULT_TIMEOUT_DURATION,
2598 );
2599 let pool = pool_raw.clone();
2600 let context = SchedulingContext::new(bank.clone());
2601 let scheduler = pool.take_scheduler(context);
2602
2603 let unfunded_keypair = Keypair::new();
2604 let bad_tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2605 &unfunded_keypair,
2606 &clone_solana_pubkey::new_rand(),
2607 2,
2608 genesis_config.hash(),
2609 ));
2610 assert_eq!(bank.transaction_count(), 0);
2611 scheduler.schedule_execution(bad_tx, 0).unwrap();
2612 sleepless_testing::at(TestCheckPoint::AfterTaskHandled);
2613 assert_eq!(bank.transaction_count(), 0);
2614
2615 let good_tx_after_bad_tx =
2616 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2617 &mint_keypair,
2618 &clone_solana_pubkey::new_rand(),
2619 3,
2620 genesis_config.hash(),
2621 ));
2622 assert_matches!(
2624 bank.simulate_transaction_unchecked(&good_tx_after_bad_tx, false)
2625 .result,
2626 Ok(_)
2627 );
2628 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2629 let bank = BankWithScheduler::new(bank, Some(scheduler));
2630 if extra_tx_after_failure {
2631 assert_matches!(
2632 bank.schedule_transaction_executions([(good_tx_after_bad_tx, 1)].into_iter()),
2633 Err(TransactionError::AccountNotFound)
2634 );
2635 }
2636 assert_eq!(bank.transaction_count(), 0);
2642
2643 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2644 assert_matches!(
2645 bank.wait_for_completed_scheduler(),
2646 Some((Err(TransactionError::AccountNotFound), _timings))
2647 );
2648
2649 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 1);
2651 sleepless_testing::at(TestCheckPoint::BeforeTrashedSchedulerCleaned);
2652
2653 sleepless_testing::at(TestCheckPoint::AfterTrashedSchedulerCleaned);
2655 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2656 }
2657
2658 #[test]
2659 fn test_scheduler_schedule_execution_failure_with_extra_tx() {
2660 do_test_scheduler_schedule_execution_failure(true);
2661 }
2662
2663 #[test]
2664 fn test_scheduler_schedule_execution_failure_without_extra_tx() {
2665 do_test_scheduler_schedule_execution_failure(false);
2666 }
2667
2668 #[test]
2669 #[should_panic(expected = "This panic should be propagated. (From: ")]
2670 fn test_scheduler_schedule_execution_panic() {
2671 clone_solana_logger::setup();
2672
2673 #[derive(Debug)]
2674 enum PanickingHanlderCheckPoint {
2675 BeforeNotifiedPanic,
2676 BeforeIgnoredPanic,
2677 }
2678
2679 let progress = sleepless_testing::setup(&[
2680 &TestCheckPoint::BeforeNewTask,
2681 &CheckPoint::NewTask(0),
2682 &PanickingHanlderCheckPoint::BeforeNotifiedPanic,
2683 &CheckPoint::SchedulerThreadAborted,
2684 &PanickingHanlderCheckPoint::BeforeIgnoredPanic,
2685 &TestCheckPoint::BeforeEndSession,
2686 ]);
2687
2688 #[derive(Debug)]
2689 struct PanickingHandler;
2690 impl TaskHandler for PanickingHandler {
2691 fn handle(
2692 _result: &mut Result<()>,
2693 _timings: &mut ExecuteTimings,
2694 _bank: &SchedulingContext,
2695 task: &Task,
2696 _handler_context: &HandlerContext,
2697 ) {
2698 let index = task.task_index();
2699 if index == 0 {
2700 sleepless_testing::at(PanickingHanlderCheckPoint::BeforeNotifiedPanic);
2701 } else if index == 1 {
2702 sleepless_testing::at(PanickingHanlderCheckPoint::BeforeIgnoredPanic);
2703 } else {
2704 unreachable!();
2705 }
2706 panic!("This panic should be propagated.");
2707 }
2708 }
2709
2710 let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
2711
2712 let bank = Bank::new_for_tests(&genesis_config);
2713 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2714
2715 const TX_COUNT: usize = 2;
2719
2720 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2721 let pool = SchedulerPool::<PooledScheduler<PanickingHandler>, _>::new_dyn(
2722 Some(TX_COUNT), None,
2724 None,
2725 None,
2726 ignored_prioritization_fee_cache,
2727 );
2728 let context = SchedulingContext::new(bank.clone());
2729
2730 let scheduler = pool.take_scheduler(context);
2731
2732 for index in 0..TX_COUNT {
2733 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2735 &Keypair::new(),
2736 &clone_solana_pubkey::new_rand(),
2737 1,
2738 genesis_config.hash(),
2739 ));
2740 scheduler.schedule_execution(tx, index).unwrap();
2741 }
2742 sleepless_testing::at(TestCheckPoint::BeforeNewTask);
2745
2746 sleepless_testing::at(TestCheckPoint::BeforeEndSession);
2747 let bank = BankWithScheduler::new(bank, Some(scheduler));
2748
2749 drop(progress);
2751 bank.wait_for_completed_scheduler().unwrap().0.unwrap();
2752 }
2753
2754 #[test]
2755 fn test_scheduler_execution_failure_short_circuiting() {
2756 clone_solana_logger::setup();
2757
2758 let _progress = sleepless_testing::setup(&[
2759 &TestCheckPoint::BeforeNewTask,
2760 &CheckPoint::NewTask(0),
2761 &CheckPoint::TaskHandled(0),
2762 &CheckPoint::SchedulerThreadAborted,
2763 &TestCheckPoint::AfterSchedulerThreadAborted,
2764 ]);
2765
2766 static TASK_COUNT: Mutex<usize> = Mutex::new(0);
2767
2768 #[derive(Debug)]
2769 struct CountingFaultyHandler;
2770 impl TaskHandler for CountingFaultyHandler {
2771 fn handle(
2772 result: &mut Result<()>,
2773 _timings: &mut ExecuteTimings,
2774 _bank: &SchedulingContext,
2775 task: &Task,
2776 _handler_context: &HandlerContext,
2777 ) {
2778 let index = task.task_index();
2779 *TASK_COUNT.lock().unwrap() += 1;
2780 if index == 1 {
2781 *result = Err(TransactionError::AccountNotFound);
2782 }
2783 sleepless_testing::at(CheckPoint::TaskHandled(index));
2784 }
2785 }
2786
2787 let GenesisConfigInfo {
2788 genesis_config,
2789 mint_keypair,
2790 ..
2791 } = create_genesis_config(10_000);
2792
2793 let bank = Bank::new_for_tests(&genesis_config);
2794 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2795 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2796 let pool = SchedulerPool::<PooledScheduler<CountingFaultyHandler>, _>::new(
2797 None,
2798 None,
2799 None,
2800 None,
2801 ignored_prioritization_fee_cache,
2802 );
2803 let context = SchedulingContext::new(bank.clone());
2804 let scheduler = pool.do_take_scheduler(context);
2805
2806 for i in 0..10 {
2807 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2808 &mint_keypair,
2809 &clone_solana_pubkey::new_rand(),
2810 2,
2811 genesis_config.hash(),
2812 ));
2813 scheduler.schedule_execution(tx, i).unwrap();
2814 }
2815 sleepless_testing::at(TestCheckPoint::BeforeNewTask);
2818
2819 let bank = BankWithScheduler::new(bank, Some(Box::new(scheduler)));
2821 assert_matches!(
2822 bank.wait_for_completed_scheduler(),
2823 Some((Err(TransactionError::AccountNotFound), _timings))
2824 );
2825 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2826 assert!(*TASK_COUNT.lock().unwrap() < 10);
2827 }
2828
2829 #[test]
2830 fn test_scheduler_schedule_execution_blocked() {
2831 clone_solana_logger::setup();
2832
2833 const STALLED_TRANSACTION_INDEX: usize = 0;
2834 const BLOCKED_TRANSACTION_INDEX: usize = 1;
2835 static LOCK_TO_STALL: Mutex<()> = Mutex::new(());
2836
2837 #[derive(Debug)]
2838 struct StallingHandler;
2839 impl TaskHandler for StallingHandler {
2840 fn handle(
2841 result: &mut Result<()>,
2842 timings: &mut ExecuteTimings,
2843 bank: &SchedulingContext,
2844 task: &Task,
2845 handler_context: &HandlerContext,
2846 ) {
2847 let index = task.task_index();
2848 match index {
2849 STALLED_TRANSACTION_INDEX => *LOCK_TO_STALL.lock().unwrap(),
2850 BLOCKED_TRANSACTION_INDEX => {}
2851 _ => unreachable!(),
2852 };
2853 DefaultTaskHandler::handle(result, timings, bank, task, handler_context);
2854 }
2855 }
2856
2857 let GenesisConfigInfo {
2858 genesis_config,
2859 mint_keypair,
2860 ..
2861 } = create_genesis_config(10_000);
2862
2863 let tx0 = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2865 &mint_keypair,
2866 &clone_solana_pubkey::new_rand(),
2867 2,
2868 genesis_config.hash(),
2869 ));
2870 let tx1 = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2871 &mint_keypair,
2872 &clone_solana_pubkey::new_rand(),
2873 2,
2874 genesis_config.hash(),
2875 ));
2876
2877 let bank = Bank::new_for_tests(&genesis_config);
2878 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2879 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2880 let pool = SchedulerPool::<PooledScheduler<StallingHandler>, _>::new_dyn(
2881 None,
2882 None,
2883 None,
2884 None,
2885 ignored_prioritization_fee_cache,
2886 );
2887 let context = SchedulingContext::new(bank.clone());
2888
2889 assert_eq!(bank.transaction_count(), 0);
2890 let scheduler = pool.take_scheduler(context);
2891
2892 let lock_to_stall = LOCK_TO_STALL.lock().unwrap();
2894 scheduler
2895 .schedule_execution(tx0, STALLED_TRANSACTION_INDEX)
2896 .unwrap();
2897 scheduler
2898 .schedule_execution(tx1, BLOCKED_TRANSACTION_INDEX)
2899 .unwrap();
2900
2901 std::thread::sleep(std::time::Duration::from_secs(1));
2903
2904 drop(lock_to_stall);
2906 let bank = BankWithScheduler::new(bank, Some(scheduler));
2907 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
2908 assert_eq!(bank.transaction_count(), 2);
2909 }
2910
2911 #[test]
2912 fn test_scheduler_mismatched_scheduling_context_race() {
2913 clone_solana_logger::setup();
2914
2915 #[derive(Debug)]
2916 struct TaskAndContextChecker;
2917 impl TaskHandler for TaskAndContextChecker {
2918 fn handle(
2919 _result: &mut Result<()>,
2920 _timings: &mut ExecuteTimings,
2921 context: &SchedulingContext,
2922 task: &Task,
2923 _handler_context: &HandlerContext,
2924 ) {
2925 assert_eq!(task.task_index() as Slot, context.bank().slot());
2927 }
2928 }
2929
2930 let GenesisConfigInfo {
2931 genesis_config,
2932 mint_keypair,
2933 ..
2934 } = create_genesis_config(10_000);
2935
2936 let bank0 = Bank::new_for_tests(&genesis_config);
2938 let bank0 = setup_dummy_fork_graph(bank0).0;
2939 let bank1 = Arc::new(Bank::new_from_parent(
2940 bank0.clone(),
2941 &Pubkey::default(),
2942 bank0.slot().checked_add(1).unwrap(),
2943 ));
2944
2945 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
2946 let pool = SchedulerPool::<PooledScheduler<TaskAndContextChecker>, _>::new(
2947 Some(4), None,
2949 None,
2950 None,
2951 ignored_prioritization_fee_cache,
2952 );
2953
2954 let dummy_tx =
2956 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2957 &mint_keypair,
2958 &clone_solana_pubkey::new_rand(),
2959 2,
2960 genesis_config.hash(),
2961 ));
2962 let context0 = &SchedulingContext::new(bank0.clone());
2963 let context1 = &SchedulingContext::new(bank1.clone());
2964
2965 for (context, index) in [(context0, 0), (context1, 1)]
2967 .into_iter()
2968 .cycle()
2969 .take(10000)
2970 {
2971 let scheduler = pool.take_scheduler(context.clone());
2972 scheduler
2973 .schedule_execution(dummy_tx.clone(), index)
2974 .unwrap();
2975 scheduler.wait_for_termination(false).1.return_to_pool();
2976 }
2977 }
2978
2979 #[derive(Debug)]
2980 struct AsyncScheduler<const TRIGGER_RACE_CONDITION: bool>(
2981 Mutex<ResultWithTimings>,
2982 Mutex<Vec<JoinHandle<ResultWithTimings>>>,
2983 SchedulingContext,
2984 Arc<SchedulerPool<Self, DefaultTaskHandler>>,
2985 );
2986
2987 impl<const TRIGGER_RACE_CONDITION: bool> AsyncScheduler<TRIGGER_RACE_CONDITION> {
2988 fn do_wait(&self) {
2989 let mut overall_result = Ok(());
2990 let mut overall_timings = ExecuteTimings::default();
2991 for handle in self.1.lock().unwrap().drain(..) {
2992 let (result, timings) = handle.join().unwrap();
2993 match result {
2994 Ok(()) => {}
2995 Err(e) => overall_result = Err(e),
2996 }
2997 overall_timings.accumulate(&timings);
2998 }
2999 *self.0.lock().unwrap() = (overall_result, overall_timings);
3000 }
3001 }
3002
3003 impl<const TRIGGER_RACE_CONDITION: bool> InstalledScheduler
3004 for AsyncScheduler<TRIGGER_RACE_CONDITION>
3005 {
3006 fn id(&self) -> SchedulerId {
3007 unimplemented!();
3008 }
3009
3010 fn context(&self) -> &SchedulingContext {
3011 &self.2
3012 }
3013
3014 fn schedule_execution(
3015 &self,
3016 transaction: RuntimeTransaction<SanitizedTransaction>,
3017 index: usize,
3018 ) -> ScheduleResult {
3019 let context = self.context().clone();
3020 let pool = self.3.clone();
3021
3022 self.1.lock().unwrap().push(std::thread::spawn(move || {
3023 std::thread::sleep(std::time::Duration::from_secs(1));
3026
3027 let mut result = Ok(());
3028 let mut timings = ExecuteTimings::default();
3029
3030 let task = SchedulingStateMachine::create_task(transaction, index, &mut |_| {
3031 UsageQueue::default()
3032 });
3033
3034 <DefaultTaskHandler as TaskHandler>::handle(
3035 &mut result,
3036 &mut timings,
3037 &context,
3038 &task,
3039 &pool.create_handler_context(
3040 BlockVerification,
3041 &crossbeam_channel::unbounded().0,
3042 ),
3043 );
3044 (result, timings)
3045 }));
3046
3047 Ok(())
3048 }
3049
3050 fn recover_error_after_abort(&mut self) -> TransactionError {
3051 unimplemented!();
3052 }
3053
3054 fn wait_for_termination(
3055 self: Box<Self>,
3056 _is_dropped: bool,
3057 ) -> (ResultWithTimings, UninstalledSchedulerBox) {
3058 self.do_wait();
3059 let result_with_timings = std::mem::replace(
3060 &mut *self.0.lock().unwrap(),
3061 initialized_result_with_timings(),
3062 );
3063 (result_with_timings, self)
3064 }
3065
3066 fn pause_for_recent_blockhash(&mut self) {
3067 if TRIGGER_RACE_CONDITION {
3068 return;
3071 }
3072 self.do_wait();
3073 }
3074 }
3075
3076 impl<const TRIGGER_RACE_CONDITION: bool> SchedulerInner for AsyncScheduler<TRIGGER_RACE_CONDITION> {
3077 fn id(&self) -> SchedulerId {
3078 42
3079 }
3080
3081 fn is_trashed(&self) -> bool {
3082 false
3083 }
3084 }
3085
3086 impl<const TRIGGER_RACE_CONDITION: bool> UninstalledScheduler
3087 for AsyncScheduler<TRIGGER_RACE_CONDITION>
3088 {
3089 fn return_to_pool(self: Box<Self>) {
3090 self.3.clone().return_scheduler(*self)
3091 }
3092 }
3093
3094 impl<const TRIGGER_RACE_CONDITION: bool> SpawnableScheduler<DefaultTaskHandler>
3095 for AsyncScheduler<TRIGGER_RACE_CONDITION>
3096 {
3097 type Inner = Self;
3099
3100 fn into_inner(self) -> (ResultWithTimings, Self::Inner) {
3101 unimplemented!();
3102 }
3103
3104 fn from_inner(
3105 _inner: Self::Inner,
3106 _context: SchedulingContext,
3107 _result_with_timings: ResultWithTimings,
3108 ) -> Self {
3109 unimplemented!();
3110 }
3111
3112 fn spawn(
3113 pool: Arc<SchedulerPool<Self, DefaultTaskHandler>>,
3114 context: SchedulingContext,
3115 _result_with_timings: ResultWithTimings,
3116 ) -> Self {
3117 AsyncScheduler::<TRIGGER_RACE_CONDITION>(
3118 Mutex::new(initialized_result_with_timings()),
3119 Mutex::new(vec![]),
3120 context,
3121 pool,
3122 )
3123 }
3124 }
3125
3126 fn do_test_scheduler_schedule_execution_recent_blockhash_edge_case<
3127 const TRIGGER_RACE_CONDITION: bool,
3128 >() {
3129 clone_solana_logger::setup();
3130
3131 let GenesisConfigInfo {
3132 genesis_config,
3133 mint_keypair,
3134 ..
3135 } = create_genesis_config(10_000);
3136 let very_old_valid_tx =
3137 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
3138 &mint_keypair,
3139 &clone_solana_pubkey::new_rand(),
3140 2,
3141 genesis_config.hash(),
3142 ));
3143 let mut bank = Bank::new_for_tests(&genesis_config);
3144 for _ in 0..MAX_PROCESSING_AGE {
3145 bank.fill_bank_with_ticks_for_tests();
3146 bank.freeze();
3147 let slot = bank.slot();
3148 bank = Bank::new_from_parent(
3149 Arc::new(bank),
3150 &Pubkey::default(),
3151 slot.checked_add(1).unwrap(),
3152 );
3153 }
3154 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
3155 let context = SchedulingContext::new(bank.clone());
3156
3157 let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
3158 let pool =
3159 SchedulerPool::<AsyncScheduler<TRIGGER_RACE_CONDITION>, DefaultTaskHandler>::new_dyn(
3160 None,
3161 None,
3162 None,
3163 None,
3164 ignored_prioritization_fee_cache,
3165 );
3166 let scheduler = pool.take_scheduler(context);
3167
3168 let bank = BankWithScheduler::new(bank, Some(scheduler));
3169 assert_eq!(bank.transaction_count(), 0);
3170
3171 bank.schedule_transaction_executions([(very_old_valid_tx, 0)].into_iter())
3173 .unwrap();
3174 bank.fill_bank_with_ticks_for_tests();
3176
3177 if TRIGGER_RACE_CONDITION {
3178 assert_matches!(
3180 bank.wait_for_completed_scheduler(),
3181 Some((Err(TransactionError::BlockhashNotFound), _))
3182 );
3183 assert_eq!(bank.transaction_count(), 0);
3184 } else {
3185 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
3186 assert_eq!(bank.transaction_count(), 1);
3187 }
3188 }
3189
3190 #[test]
3191 fn test_scheduler_schedule_execution_recent_blockhash_edge_case_with_race() {
3192 do_test_scheduler_schedule_execution_recent_blockhash_edge_case::<true>();
3193 }
3194
3195 #[test]
3196 fn test_scheduler_schedule_execution_recent_blockhash_edge_case_without_race() {
3197 do_test_scheduler_schedule_execution_recent_blockhash_edge_case::<false>();
3198 }
3199
3200 #[test]
3201 fn test_default_handler_count() {
3202 for (detected, expected) in [(32, 8), (4, 1), (2, 1)] {
3203 assert_eq!(
3204 DefaultSchedulerPool::calculate_default_handler_count(Some(detected)),
3205 expected
3206 );
3207 }
3208 assert_eq!(
3209 DefaultSchedulerPool::calculate_default_handler_count(None),
3210 4
3211 );
3212 }
3213
3214 #[test]
3216 fn test_enfoced_get_account_locks_validation() {
3217 clone_solana_logger::setup();
3218
3219 let GenesisConfigInfo {
3220 genesis_config,
3221 ref mint_keypair,
3222 ..
3223 } = create_genesis_config(10_000);
3224 let bank = Bank::new_for_tests(&genesis_config);
3225 let (bank, _bank_forks) = &setup_dummy_fork_graph(bank);
3226
3227 let mut tx = system_transaction::transfer(
3228 mint_keypair,
3229 &clone_solana_pubkey::new_rand(),
3230 2,
3231 genesis_config.hash(),
3232 );
3233 tx.message.account_keys.push(tx.message.account_keys[0]);
3235 let tx = RuntimeTransaction::from_transaction_for_tests(tx);
3236
3237 let result = &mut Ok(());
3239 let timings = &mut ExecuteTimings::default();
3240 let prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
3241 let scheduling_context = &SchedulingContext::new(bank.clone());
3242 let handler_context = &HandlerContext {
3243 log_messages_bytes_limit: None,
3244 transaction_status_sender: None,
3245 replay_vote_sender: None,
3246 prioritization_fee_cache,
3247 banking_packet_receiver: never(),
3248 banking_packet_handler: Box::new(|_, _| {}),
3249 banking_stage_helper: None,
3250 transaction_recorder: None,
3251 };
3252
3253 let task = SchedulingStateMachine::create_task(tx, 0, &mut |_| UsageQueue::default());
3254 DefaultTaskHandler::handle(result, timings, scheduling_context, &task, handler_context);
3255 assert_matches!(result, Err(TransactionError::AccountLoadedTwice));
3256 }
3257
3258 enum TxResult {
3259 ExecutedWithSuccess,
3260 ExecutedWithFailure,
3261 NotExecuted,
3262 }
3263
3264 #[test_matrix(
3265 [TxResult::ExecutedWithSuccess, TxResult::ExecutedWithFailure, TxResult::NotExecuted],
3266 [false, true]
3267 )]
3268 fn test_task_handler_poh_recording(tx_result: TxResult, should_succeed_to_record_to_poh: bool) {
3269 clone_solana_logger::setup();
3270
3271 let GenesisConfigInfo {
3272 genesis_config,
3273 ref mint_keypair,
3274 ..
3275 } = clone_solana_ledger::genesis_utils::create_genesis_config(10_000);
3276 let bank = Bank::new_for_tests(&genesis_config);
3277 let bank_forks = BankForks::new_rw_arc(bank);
3278 let bank = bank_forks.read().unwrap().working_bank_with_scheduler();
3279
3280 let (tx, expected_tx_result) = match tx_result {
3281 TxResult::ExecutedWithSuccess => (
3282 system_transaction::transfer(
3283 mint_keypair,
3284 &clone_solana_pubkey::new_rand(),
3285 1,
3286 genesis_config.hash(),
3287 ),
3288 Ok(()),
3289 ),
3290 TxResult::ExecutedWithFailure => (
3291 system_transaction::transfer(
3292 mint_keypair,
3293 &clone_solana_pubkey::new_rand(),
3294 1_000_000,
3295 genesis_config.hash(),
3296 ),
3297 Ok(()),
3298 ),
3299 TxResult::NotExecuted => (
3300 system_transaction::transfer(
3301 mint_keypair,
3302 &clone_solana_pubkey::new_rand(),
3303 1,
3304 Hash::default(),
3305 ),
3306 Err(TransactionError::BlockhashNotFound),
3307 ),
3308 };
3309 let tx = RuntimeTransaction::from_transaction_for_tests(tx);
3310
3311 let result = &mut Ok(());
3312 let timings = &mut ExecuteTimings::default();
3313 let prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64));
3314 let scheduling_context = &SchedulingContext::for_production(bank.clone());
3315 let (sender, receiver) = crossbeam_channel::unbounded();
3316 let (ledger_path, _blockhash) = create_new_tmp_ledger_auto_delete!(&genesis_config);
3317 let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap());
3318 let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
3319 let (exit, poh_recorder, poh_service, signal_receiver) =
3320 create_test_recorder_with_index_tracking(
3321 bank.clone(),
3322 blockstore.clone(),
3323 None,
3324 Some(leader_schedule_cache),
3325 );
3326 let handler_context = &HandlerContext {
3327 log_messages_bytes_limit: None,
3328 transaction_status_sender: Some(TransactionStatusSender { sender }),
3329 replay_vote_sender: None,
3330 prioritization_fee_cache,
3331 banking_packet_receiver: never(),
3332 banking_packet_handler: Box::new(|_, _| {}),
3333 banking_stage_helper: None,
3334 transaction_recorder: Some(poh_recorder.read().unwrap().new_recorder()),
3335 };
3336
3337 let task =
3338 SchedulingStateMachine::create_task(tx.clone(), 0, &mut |_| UsageQueue::default());
3339
3340 if !should_succeed_to_record_to_poh {
3343 while poh_recorder.read().unwrap().bank().is_some() {
3344 sleep(Duration::from_millis(100));
3345 }
3346 while signal_receiver.try_recv().is_ok() {}
3347 }
3348
3349 assert_eq!(bank.transaction_count(), 0);
3350 assert_eq!(bank.transaction_error_count(), 0);
3351 DefaultTaskHandler::handle(result, timings, scheduling_context, &task, handler_context);
3352
3353 if should_succeed_to_record_to_poh {
3354 if expected_tx_result.is_ok() {
3355 assert_matches!(result, Ok(()));
3356 assert_eq!(bank.transaction_count(), 1);
3357 if matches!(tx_result, TxResult::ExecutedWithFailure) {
3358 assert_eq!(bank.transaction_error_count(), 1);
3359 } else {
3360 assert_eq!(bank.transaction_error_count(), 0);
3361 }
3362 assert_matches!(
3363 receiver.try_recv(),
3364 Ok(TransactionStatusMessage::Batch(
3365 TransactionStatusBatch { .. }
3366 ))
3367 );
3368 assert_matches!(
3369 signal_receiver.try_recv(),
3370 Ok((_, (clone_solana_entry::entry::Entry {transactions, ..} , _)))
3371 if transactions == vec![tx.to_versioned_transaction()]
3372 );
3373 } else {
3374 assert_eq!(result, &expected_tx_result);
3375 assert_eq!(bank.transaction_count(), 0);
3376 assert_eq!(bank.transaction_error_count(), 0);
3377 assert_matches!(receiver.try_recv(), Err(_));
3378 assert_matches!(signal_receiver.try_recv(), Err(_));
3379 }
3380 } else {
3381 if expected_tx_result.is_ok() {
3382 assert_matches!(result, Err(TransactionError::CommitCancelled));
3383 } else {
3384 assert_eq!(result, &expected_tx_result);
3385 }
3386
3387 assert_eq!(bank.transaction_count(), 0);
3388 assert_matches!(receiver.try_recv(), Err(_));
3389 assert_matches!(signal_receiver.try_recv(), Err(_));
3390 }
3391
3392 exit.store(true, Ordering::Relaxed);
3393 poh_service.join().unwrap();
3394 }
3395}