1#![cfg(feature = "agave-unstable-api")]
2use {
17 assert_matches::assert_matches,
18 crossbeam_channel::{
19 self, Receiver, RecvError, RecvTimeoutError, SendError, Sender, never, select_biased,
20 },
21 dashmap::DashMap,
22 derive_where::derive_where,
23 log::*,
24 scopeguard::defer,
25 solana_clock::Slot,
26 solana_ledger::blockstore_processor::{
27 TransactionBatchWithIndexes, TransactionStatusSender, execute_batch,
28 },
29 solana_pubkey::Pubkey,
30 solana_runtime::{
31 installed_scheduler_pool::{
32 InstalledScheduler, InstalledSchedulerBox, InstalledSchedulerPool, ResultWithTimings,
33 ScheduleResult, SchedulerAborted, SchedulerId, SchedulingContext, TimeoutListener,
34 UninstalledScheduler, UninstalledSchedulerBox, initialized_result_with_timings,
35 },
36 prioritization_fee_cache::PrioritizationFeeCache,
37 vote_sender_types::{ReplayVoteSendType, ReplayVoteSender},
38 },
39 solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
40 solana_svm_timings::ExecuteTimings,
41 solana_transaction::sanitized::SanitizedTransaction,
42 solana_transaction_error::{TransactionError, TransactionResult as Result},
43 solana_unified_scheduler_logic::{
44 BlockSize, Capability, OrderedTaskId, SchedulingStateMachine, Task, UsageQueue,
45 },
46 static_assertions::const_assert_eq,
47 std::{
48 fmt::Debug,
49 marker::PhantomData,
50 mem,
51 sync::{
52 Arc, Mutex, OnceLock, Weak,
53 atomic::{AtomicU64, Ordering::Relaxed},
54 },
55 thread::{self, JoinHandle, sleep},
56 time::{Duration, Instant},
57 },
58 unwrap_none::UnwrapNone,
59};
60
61mod sleepless_testing;
62use crate::sleepless_testing::BuilderTracked;
63
64#[allow(dead_code)]
66#[derive(Debug)]
67enum CheckPoint<'a> {
68 NewTask(OrderedTaskId),
69 NewBufferedOrDroppedTask(OrderedTaskId),
70 BufferedOrDroppedTask(OrderedTaskId),
71 TaskHandled(OrderedTaskId),
72 TaskAccumulated(OrderedTaskId, &'a Result<()>),
73 SessionEnding,
74 SessionFinished(Slot),
75 SchedulerThreadAborted,
76 IdleSchedulerCleaned(usize),
77 IdlingSchedulerTrashed,
78 ReturningSchedulerTrashed,
79 TrashedSchedulerCleaned(usize),
80 TimeoutListenerTriggered(usize),
81 DiscardRequested,
82 Discarded(usize),
83}
84
85type CountOrDefault = Option<usize>;
86type AtomicSchedulerId = AtomicU64;
87
88#[derive(Debug)]
103pub struct SchedulerPool<S: SpawnableScheduler<TH>, TH: TaskHandler> {
104 scheduler_inners: Mutex<Vec<(S::Inner, Instant)>>,
105 trashed_scheduler_inners: Mutex<Vec<S::Inner>>,
106 timeout_listeners: Mutex<Vec<(TimeoutListener, Instant)>>,
107 common_handler_context: CommonHandlerContext,
108 block_verification_handler_count: CountOrDefault,
109 weak_self: Weak<Self>,
120 next_scheduler_id: AtomicSchedulerId,
121 max_usage_queue_count: usize,
122 scheduler_pool_sender: Sender<Weak<Self>>,
123 cleaner_thread: JoinHandle<()>,
124 _phantom: PhantomData<TH>,
125}
126
127#[derive(derive_more::Debug, Clone)]
128pub struct HandlerContext {
129 thread_count: usize,
130 log_messages_bytes_limit: Option<usize>,
131 transaction_status_sender: Option<TransactionStatusSender>,
132 replay_vote_sender: Option<ReplayVoteSender>,
133 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
134}
135
136impl HandlerContext {
137 fn usage_queue_loader_for_newly_spawned(&self) -> UsageQueueLoader {
138 UsageQueueLoader::OwnedBySelf {
139 usage_queue_loader_inner: UsageQueueLoaderInner::new(Capability::FifoQueueing),
140 }
141 }
142
143 fn clone_for_scheduler_thread(&self) -> Self {
144 self.clone()
145 }
146}
147
148#[derive(Debug, Clone)]
149struct CommonHandlerContext {
150 log_messages_bytes_limit: Option<usize>,
151 transaction_status_sender: Option<TransactionStatusSender>,
152 replay_vote_sender: Option<ReplayVoteSender>,
153 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
154}
155
156impl CommonHandlerContext {
157 fn into_handler_context(self, thread_count: usize) -> HandlerContext {
158 let Self {
159 log_messages_bytes_limit,
160 transaction_status_sender,
161 replay_vote_sender,
162 prioritization_fee_cache,
163 } = self;
164
165 HandlerContext {
166 thread_count,
167 log_messages_bytes_limit,
168 transaction_status_sender,
169 replay_vote_sender,
170 prioritization_fee_cache,
171 }
172 }
173}
174
175pub type DefaultSchedulerPool =
176 SchedulerPool<PooledScheduler<DefaultTaskHandler>, DefaultTaskHandler>;
177
178const DEFAULT_POOL_CLEANER_INTERVAL: Duration = Duration::from_secs(10);
179const DEFAULT_MAX_POOLING_DURATION: Duration = Duration::from_secs(180);
180const DEFAULT_TIMEOUT_DURATION: Duration = Duration::from_secs(12);
181const DEFAULT_MAX_USAGE_QUEUE_COUNT: usize = 262_144;
196
197impl<S, TH> SchedulerPool<S, TH>
198where
199 S: SpawnableScheduler<TH>,
200 TH: TaskHandler,
201{
202 pub fn new(
203 block_verification_handler_count: CountOrDefault,
204 log_messages_bytes_limit: Option<usize>,
205 transaction_status_sender: Option<TransactionStatusSender>,
206 replay_vote_sender: Option<ReplayVoteSender>,
207 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
208 ) -> Arc<Self> {
209 Self::do_new(
210 block_verification_handler_count,
211 log_messages_bytes_limit,
212 transaction_status_sender,
213 replay_vote_sender,
214 prioritization_fee_cache,
215 DEFAULT_POOL_CLEANER_INTERVAL,
216 DEFAULT_MAX_POOLING_DURATION,
217 DEFAULT_MAX_USAGE_QUEUE_COUNT,
218 DEFAULT_TIMEOUT_DURATION,
219 )
220 }
221
222 #[cfg(feature = "dev-context-only-utils")]
223 pub fn new_for_verification(
224 block_verification_handler_count: CountOrDefault,
225 log_messages_bytes_limit: Option<usize>,
226 transaction_status_sender: Option<TransactionStatusSender>,
227 replay_vote_sender: Option<ReplayVoteSender>,
228 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
229 ) -> Arc<Self> {
230 Self::new(
231 block_verification_handler_count,
232 log_messages_bytes_limit,
233 transaction_status_sender,
234 replay_vote_sender,
235 prioritization_fee_cache,
236 )
237 }
238
239 #[allow(clippy::too_many_arguments)]
240 fn do_new(
241 block_verification_handler_count: CountOrDefault,
242 log_messages_bytes_limit: Option<usize>,
243 transaction_status_sender: Option<TransactionStatusSender>,
244 replay_vote_sender: Option<ReplayVoteSender>,
245 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
246 pool_cleaner_interval: Duration,
247 max_pooling_duration: Duration,
248 max_usage_queue_count: usize,
249 timeout_duration: Duration,
250 ) -> Arc<Self> {
251 let (scheduler_pool_sender, scheduler_pool_receiver) = crossbeam_channel::bounded(1);
252
253 let cleaner_main_loop = move || {
254 info!("cleaner_main_loop: started...");
255
256 let weak_scheduler_pool: Weak<Self> = scheduler_pool_receiver.recv().unwrap();
257 loop {
258 match scheduler_pool_receiver.recv_timeout(pool_cleaner_interval) {
259 Ok(_) => unreachable!(),
260 Err(RecvTimeoutError::Disconnected | RecvTimeoutError::Timeout) => (),
261 }
262
263 let Some(scheduler_pool) = weak_scheduler_pool.upgrade() else {
264 break;
267 };
268
269 let now = Instant::now();
270
271 let idle_inner_count = {
272 let mut idle_inners = Vec::with_capacity(128);
274
275 let Ok(mut scheduler_inners) = scheduler_pool.scheduler_inners.lock() else {
276 break;
277 };
278 idle_inners.extend(scheduler_inners.extract_if(.., |(_inner, pooled_at)| {
281 now.duration_since(*pooled_at) > max_pooling_duration
282 }));
283 drop(scheduler_inners);
284
285 let idle_inner_count = idle_inners.len();
286 drop(idle_inners);
287 idle_inner_count
288 };
289
290 let trashed_inner_count = {
291 let Ok(mut trashed_inners) = scheduler_pool.trashed_scheduler_inners.lock()
292 else {
293 break;
294 };
295 let trashed_inner_count = trashed_inners.len();
296 let trashed_inners: Vec<_> = mem::take(&mut *trashed_inners);
297 drop(trashed_inners);
299 trashed_inner_count
300 };
301
302 let triggered_timeout_listener_count = {
303 let mut expired_listeners = Vec::with_capacity(128);
305 let Ok(mut timeout_listeners) = scheduler_pool.timeout_listeners.lock() else {
306 break;
307 };
308 expired_listeners.extend(timeout_listeners.extract_if(
309 ..,
310 |(_callback, registered_at)| {
311 now.duration_since(*registered_at) > timeout_duration
312 },
313 ));
314 drop(timeout_listeners);
315
316 let count = expired_listeners.len();
317 for (timeout_listener, _registered_at) in expired_listeners {
321 timeout_listener.trigger(scheduler_pool.clone());
322 }
323 count
324 };
325
326 info!(
327 "Scheduler pool cleaner: dropped {idle_inner_count} idle inners, \
328 {trashed_inner_count} trashed inners, triggered \
329 {triggered_timeout_listener_count} timeout listeners",
330 );
331 sleepless_testing::at(CheckPoint::IdleSchedulerCleaned(idle_inner_count));
332 sleepless_testing::at(CheckPoint::TrashedSchedulerCleaned(trashed_inner_count));
333 sleepless_testing::at(CheckPoint::TimeoutListenerTriggered(
334 triggered_timeout_listener_count,
335 ));
336 }
337 info!("cleaner_main_loop: ...finished");
338 };
339
340 let cleaner_thread = thread::Builder::new()
341 .name("solScCleaner".to_owned())
342 .spawn_tracked(cleaner_main_loop)
343 .unwrap();
344
345 let scheduler_pool = Arc::new_cyclic(|weak_self| Self {
346 scheduler_inners: Mutex::default(),
347 trashed_scheduler_inners: Mutex::default(),
348 timeout_listeners: Mutex::default(),
349 common_handler_context: CommonHandlerContext {
350 log_messages_bytes_limit,
351 transaction_status_sender,
352 replay_vote_sender,
353 prioritization_fee_cache,
354 },
355 block_verification_handler_count,
356 weak_self: weak_self.clone(),
357 next_scheduler_id: AtomicSchedulerId::default(),
358 max_usage_queue_count,
359 scheduler_pool_sender: scheduler_pool_sender.clone(),
360 cleaner_thread,
361 _phantom: PhantomData,
362 });
363
364 scheduler_pool_sender
365 .send(Arc::downgrade(&scheduler_pool))
366 .unwrap();
367
368 scheduler_pool
369 }
370
371 fn self_arc(&self) -> Arc<Self> {
373 self.weak_self
374 .upgrade()
375 .expect("self-referencing Arc-ed pool")
376 }
377
378 fn new_scheduler_id(&self) -> SchedulerId {
379 self.next_scheduler_id.fetch_add(1, Relaxed)
380 }
381
382 fn return_scheduler(&self, scheduler: S::Inner) {
385 let should_trash = scheduler.is_trashed();
388
389 if should_trash {
390 self.trashed_scheduler_inners
398 .lock()
399 .expect("not poisoned")
400 .push(scheduler);
401 sleepless_testing::at(CheckPoint::ReturningSchedulerTrashed);
402 } else {
403 self.scheduler_inners
404 .lock()
405 .expect("not poisoned")
406 .push((scheduler, Instant::now()));
407 }
408 }
409
410 #[cfg(test)]
411 fn do_take_scheduler(&self, context: SchedulingContext) -> S {
412 self.do_take_resumed_scheduler(context, initialized_result_with_timings())
413 .unwrap()
414 }
415
416 fn do_take_resumed_scheduler(
417 &self,
418 context: SchedulingContext,
419 result_with_timings: ResultWithTimings,
420 ) -> Option<S> {
421 assert_matches!(result_with_timings, (Ok(_), _));
422
423 if let Some((inner, _pooled_at)) = self.scheduler_inners.lock().expect("not poisoned").pop()
426 {
427 Some(S::from_inner(inner, context, result_with_timings))
428 } else {
429 Some(S::spawn(self.self_arc(), context, result_with_timings))
430 }
431 }
432
433 #[cfg(feature = "dev-context-only-utils")]
434 pub fn pooled_scheduler_count(&self) -> usize {
435 self.scheduler_inners.lock().expect("not poisoned").len()
436 }
437
438 fn create_handler_context(&self) -> HandlerContext {
439 let thread_count = self.block_verification_handler_count;
440
441 let thread_count = thread_count.unwrap_or(Self::default_handler_count());
442 assert!(thread_count >= 1);
443 self.common_handler_context
444 .clone()
445 .into_handler_context(thread_count)
446 }
447
448 pub fn default_handler_count() -> usize {
449 Self::calculate_default_handler_count(
450 thread::available_parallelism()
451 .ok()
452 .map(|non_zero| non_zero.get()),
453 )
454 }
455
456 pub fn calculate_default_handler_count(detected_cpu_core_count: CountOrDefault) -> usize {
457 detected_cpu_core_count
463 .map(|core_count| (core_count / 4).max(1))
464 .unwrap_or(4)
465 }
466
467 pub fn cli_message() -> &'static str {
468 static MESSAGE: OnceLock<String> = OnceLock::new();
469
470 MESSAGE.get_or_init(|| {
471 format!(
472 "Change the number of the unified scheduler's transaction execution threads \
473 dedicated to each block, otherwise calculated as cpu_cores/4 [default: {}]",
474 Self::default_handler_count()
475 )
476 })
477 }
478}
479
480impl<S, TH> InstalledSchedulerPool for SchedulerPool<S, TH>
481where
482 S: SpawnableScheduler<TH>,
483 TH: TaskHandler,
484{
485 fn take_resumed_scheduler(
486 &self,
487 context: SchedulingContext,
488 result_with_timings: ResultWithTimings,
489 ) -> Option<InstalledSchedulerBox> {
490 self.do_take_resumed_scheduler(context, result_with_timings)
491 .map(|s| Box::new(s) as InstalledSchedulerBox)
492 }
493
494 fn register_timeout_listener(&self, timeout_listener: TimeoutListener) {
495 self.timeout_listeners
496 .lock()
497 .unwrap()
498 .push((timeout_listener, Instant::now()));
499 }
500
501 fn uninstalled_from_bank_forks(self: Arc<Self>) {
502 info!("SchedulerPool::uninstalled_from_bank_forks(): started...");
503
504 for (listener, _registered_at) in mem::take(&mut *self.timeout_listeners.lock().unwrap()) {
506 listener.trigger(self.clone());
507 }
508
509 mem::take(&mut *self.scheduler_inners.lock().unwrap());
511 mem::take(&mut *self.trashed_scheduler_inners.lock().unwrap());
512
513 let mut this = self;
520 let mut this: Self = loop {
521 match Arc::try_unwrap(this) {
522 Ok(pool) => {
523 break pool;
524 }
525 Err(that) => {
526 this = that;
528 sleep(Duration::from_millis(100));
529 continue;
532 }
533 }
534 };
535 this.scheduler_pool_sender = crossbeam_channel::bounded(1).0;
537 this.cleaner_thread.join().unwrap();
538
539 info!("SchedulerPool::uninstalled_from_bank_forks(): ...finished");
540 }
541}
542
543pub trait TaskHandler: Send + Sync + Debug + Sized + 'static {
544 fn handle(
545 result: &mut Result<()>,
546 timings: &mut ExecuteTimings,
547 scheduling_context: &SchedulingContext,
548 task: &Task,
549 handler_context: &HandlerContext,
550 );
551}
552
553#[derive(Debug)]
554pub struct DefaultTaskHandler;
555
556impl TaskHandler for DefaultTaskHandler {
557 fn handle(
558 result: &mut Result<()>,
559 timings: &mut ExecuteTimings,
560 scheduling_context: &SchedulingContext,
561 task: &Task,
562 handler_context: &HandlerContext,
563 ) {
564 let bank = scheduling_context.bank();
565 let transaction = task.transaction();
566 let task_id = task.task_id();
567
568 let batch = bank.prepare_unlocked_batch_from_single_tx(transaction);
569
570 let transaction_indexes = vec![task_id.try_into().unwrap()];
571 let batch_with_indexes = TransactionBatchWithIndexes {
572 batch,
573 transaction_indexes,
574 };
575
576 *result = execute_batch(
577 &batch_with_indexes,
578 bank,
579 handler_context.transaction_status_sender.as_ref(),
580 handler_context.replay_vote_sender.as_ref(),
581 ReplayVoteSendType::Executed {
582 replay_bank_id: bank.bank_id(),
583 replay_slot: bank.slot(),
584 },
585 timings,
586 handler_context.log_messages_bytes_limit,
587 handler_context.prioritization_fee_cache.as_deref(),
588 None::<fn(&_) -> _>,
589 );
590 sleepless_testing::at(CheckPoint::TaskHandled(task_id));
591 }
592}
593
594struct ExecutedTask {
595 task: Task,
596 result_with_timings: ResultWithTimings,
597}
598
599impl ExecutedTask {
600 fn new_boxed(task: Task) -> Box<Self> {
601 Box::new(Self {
602 task,
603 result_with_timings: initialized_result_with_timings(),
604 })
605 }
606
607 fn consumed_block_size(&self) -> BlockSize {
608 self.task.consumed_block_size()
609 }
610}
611
612enum SubchanneledPayload<P1, P2> {
619 Payload(P1),
620 OpenSubchannel(P2),
621 UnpauseOpenedSubchannel,
622 CloseSubchannel,
623 Reset,
624 Disconnect,
625}
626
627type NewTaskPayload = SubchanneledPayload<Task, Box<(SchedulingContext, ResultWithTimings)>>;
628const_assert_eq!(mem::size_of::<NewTaskPayload>(), 16);
629
630mod chained_channel {
652 use super::*;
653
654 enum ChainedChannelPrivate<P, C> {
656 Payload(P),
657 ContextAndChannels(C, Receiver<ChainedChannel<P, C>>, Receiver<P>),
658 }
659
660 pub(super) struct ChainedChannel<P, C>(ChainedChannelPrivate<P, C>);
661
662 impl<P, C> ChainedChannel<P, C> {
663 fn chain_to_new_channel(
664 context: C,
665 receiver: Receiver<Self>,
666 aux_receiver: Receiver<P>,
667 ) -> Self {
668 Self(ChainedChannelPrivate::ContextAndChannels(
669 context,
670 receiver,
671 aux_receiver,
672 ))
673 }
674 }
675
676 pub(super) struct ChainedChannelSender<P, C> {
677 sender: Sender<ChainedChannel<P, C>>,
678 aux_sender: Sender<P>,
679 }
680
681 impl<P, C: Clone> ChainedChannelSender<P, C> {
682 fn new(sender: Sender<ChainedChannel<P, C>>, aux_sender: Sender<P>) -> Self {
683 Self { sender, aux_sender }
684 }
685
686 pub(super) fn send_payload(
687 &self,
688 payload: P,
689 ) -> std::result::Result<(), SendError<ChainedChannel<P, C>>> {
690 self.sender
691 .send(ChainedChannel(ChainedChannelPrivate::Payload(payload)))
692 }
693
694 pub(super) fn send_aux_payload(&self, payload: P) -> std::result::Result<(), SendError<P>> {
695 self.aux_sender.send(payload)
696 }
697
698 pub(super) fn send_chained_channel(
699 &mut self,
700 context: &C,
701 count: usize,
702 ) -> std::result::Result<(), SendError<ChainedChannel<P, C>>> {
703 let (chained_sender, chained_receiver) = crossbeam_channel::unbounded();
704 let (chained_aux_sender, chained_aux_receiver) = crossbeam_channel::unbounded();
705 for _ in 0..count {
706 self.sender.send(ChainedChannel::chain_to_new_channel(
707 context.clone(),
708 chained_receiver.clone(),
709 chained_aux_receiver.clone(),
710 ))?
711 }
712 self.sender = chained_sender;
713 self.aux_sender = chained_aux_sender;
714 Ok(())
715 }
716 }
717
718 #[derive_where(Clone)]
721 pub(super) struct ChainedChannelReceiver<P, C: Clone> {
722 receiver: Receiver<ChainedChannel<P, C>>,
723 aux_receiver: Receiver<P>,
724 context: C,
725 }
726
727 impl<P, C: Clone> ChainedChannelReceiver<P, C> {
728 fn new(
729 receiver: Receiver<ChainedChannel<P, C>>,
730 aux_receiver: Receiver<P>,
731 initial_context: C,
732 ) -> Self {
733 Self {
734 receiver,
735 aux_receiver,
736 context: initial_context,
737 }
738 }
739
740 pub(super) fn context(&self) -> &C {
741 &self.context
742 }
743
744 pub(super) fn for_select(&self) -> &Receiver<ChainedChannel<P, C>> {
745 &self.receiver
746 }
747
748 pub(super) fn aux_for_select(&self) -> &Receiver<P> {
749 &self.aux_receiver
750 }
751
752 pub(super) fn never_receive_from_aux(&mut self) {
753 self.aux_receiver = never();
754 }
755
756 pub(super) fn after_select(&mut self, message: ChainedChannel<P, C>) -> Option<P> {
757 match message.0 {
758 ChainedChannelPrivate::Payload(payload) => Some(payload),
759 ChainedChannelPrivate::ContextAndChannels(context, channel, idle_channel) => {
760 self.context = context;
761 self.receiver = channel;
762 self.aux_receiver = idle_channel;
763 None
764 }
765 }
766 }
767 }
768
769 pub(super) fn unbounded<P, C: Clone>(
770 initial_context: C,
771 ) -> (ChainedChannelSender<P, C>, ChainedChannelReceiver<P, C>) {
772 let (sender, receiver) = crossbeam_channel::unbounded();
773 let (aux_sender, aux_receiver) = crossbeam_channel::unbounded();
774 (
775 ChainedChannelSender::new(sender, aux_sender),
776 ChainedChannelReceiver::new(receiver, aux_receiver, initial_context),
777 )
778 }
779}
780
781#[derive(Debug)]
792struct UsageQueueLoaderInner {
793 capability: Capability,
794 usage_queues: DashMap<Pubkey, UsageQueue>,
795}
796
797impl UsageQueueLoaderInner {
798 fn new(capability: Capability) -> Self {
799 Self {
800 capability,
801 usage_queues: DashMap::default(),
802 }
803 }
804
805 fn load(&self, address: Pubkey) -> UsageQueue {
806 self.usage_queues
807 .entry(address)
808 .or_insert_with(|| UsageQueue::new(&self.capability))
809 .clone()
810 }
811
812 fn count(&self) -> usize {
813 self.usage_queues.len()
814 }
815}
816
817#[derive(Debug)]
821enum UsageQueueLoader {
822 OwnedBySelf {
824 usage_queue_loader_inner: UsageQueueLoaderInner,
825 },
826}
827
828impl UsageQueueLoader {
829 fn usage_queue_loader(&self) -> &UsageQueueLoaderInner {
830 match self {
831 Self::OwnedBySelf {
832 usage_queue_loader_inner,
833 } => usage_queue_loader_inner,
834 }
835 }
836
837 fn load(&self, pubkey: Pubkey) -> UsageQueue {
838 self.usage_queue_loader().load(pubkey)
839 }
840
841 fn is_overgrown(&self, max_usage_queue_count: usize) -> bool {
842 if self.usage_queue_loader().count() > max_usage_queue_count {
843 return true;
844 }
845
846 match self {
847 Self::OwnedBySelf {
848 usage_queue_loader_inner: _,
849 } => false,
850 }
851 }
852}
853
854fn disconnected<T>() -> Receiver<T> {
858 crossbeam_channel::unbounded().1
860}
861
862#[cfg_attr(doc, aquamarine::aquamarine)]
863#[derive(Debug)]
930pub struct PooledScheduler<TH: TaskHandler> {
931 inner: PooledSchedulerInner<Self, TH>,
932 context: SchedulingContext,
933}
934
935#[derive(Debug)]
936pub struct PooledSchedulerInner<S: SpawnableScheduler<TH>, TH: TaskHandler> {
937 thread_manager: ThreadManager<S, TH>,
938 usage_queue_loader: UsageQueueLoader,
939}
940
941impl<S, TH> Drop for ThreadManager<S, TH>
942where
943 S: SpawnableScheduler<TH>,
944 TH: TaskHandler,
945{
946 fn drop(&mut self) {
947 trace!("ThreadManager::drop() is called...");
948
949 if self.are_threads_joined() {
950 return;
951 }
952 if thread::panicking() {
957 error!(
958 "ThreadManager::drop(): scheduler_id: {} skipping due to already panicking...",
959 self.scheduler_id,
960 );
961 return;
962 }
963
964 assert_matches!(self.session_result_with_timings, None);
966
967 let abort_detected = self.disconnect_new_task_sender();
969
970 if abort_detected {
971 self.ensure_join_threads_after_abort(true);
972 } else {
973 self.ensure_join_threads(true);
974 }
975 assert_matches!(self.session_result_with_timings, Some((Ok(_), _)));
979 }
980}
981
982impl<S, TH> PooledSchedulerInner<S, TH>
983where
984 S: SpawnableScheduler<TH>,
985 TH: TaskHandler,
986{
987 fn is_aborted(&self) -> bool {
988 self.thread_manager.are_threads_joined()
1006 }
1007}
1008
1009#[derive(Debug)]
1015struct ThreadManager<S: SpawnableScheduler<TH>, TH: TaskHandler> {
1016 scheduler_id: SchedulerId,
1017 pool: Arc<SchedulerPool<S, TH>>,
1018 new_task_sender: Sender<NewTaskPayload>,
1019 new_task_receiver: Option<Receiver<NewTaskPayload>>,
1020 session_result_sender: Sender<ResultWithTimings>,
1021 session_result_receiver: Receiver<ResultWithTimings>,
1022 session_result_with_timings: Option<ResultWithTimings>,
1023 scheduler_thread: Option<JoinHandle<()>>,
1024 handler_threads: Vec<JoinHandle<()>>,
1025}
1026
1027struct HandlerPanicked;
1028type HandlerResult = std::result::Result<Box<ExecutedTask>, HandlerPanicked>;
1029
1030impl<S: SpawnableScheduler<TH>, TH: TaskHandler> ThreadManager<S, TH> {
1031 fn new(pool: Arc<SchedulerPool<S, TH>>) -> Self {
1032 let (new_task_sender, new_task_receiver) = crossbeam_channel::unbounded();
1033 let (session_result_sender, session_result_receiver) = crossbeam_channel::unbounded();
1034
1035 Self {
1036 scheduler_id: pool.new_scheduler_id(),
1037 pool,
1038 new_task_sender,
1039 new_task_receiver: Some(new_task_receiver),
1040 session_result_sender,
1041 session_result_receiver,
1042 session_result_with_timings: None,
1043 scheduler_thread: None,
1044 handler_threads: vec![],
1045 }
1046 }
1047
1048 fn execute_task_with_handler(
1049 scheduling_context: &SchedulingContext,
1050 executed_task: &mut Box<ExecutedTask>,
1051 handler_context: &HandlerContext,
1052 ) {
1053 debug!("handling task at {:?}", thread::current());
1054 TH::handle(
1055 &mut executed_task.result_with_timings.0,
1056 &mut executed_task.result_with_timings.1,
1057 scheduling_context,
1058 &executed_task.task,
1059 handler_context,
1060 );
1061 }
1062
1063 fn max_running_task_count() -> Option<usize> {
1064 None
1070 }
1071
1072 fn max_unique_active_task_count() -> Option<usize> {
1073 None
1078 }
1079
1080 fn can_receive_unblocked_task(state_machine: &SchedulingStateMachine) -> bool {
1081 state_machine.has_unblocked_task()
1085 }
1086
1087 fn can_finish_session(session_ending: bool, state_machine: &SchedulingStateMachine) -> bool {
1088 session_ending && state_machine.has_no_active_task()
1092 }
1093
1094 #[must_use]
1096 fn abort_or_accumulate_result_with_timings(
1097 (result, timings): &mut ResultWithTimings,
1098 executed_task: Box<ExecutedTask>,
1099 ) -> bool {
1100 sleepless_testing::at(CheckPoint::TaskAccumulated(
1101 executed_task.task.task_id(),
1102 &executed_task.result_with_timings.0,
1103 ));
1104 timings.accumulate(&executed_task.result_with_timings.1);
1105
1106 match executed_task.result_with_timings.0 {
1107 Ok(()) => {
1108 assert_eq!(executed_task.consumed_block_size(), 0);
1111
1112 false
1113 }
1114 Err(TransactionError::AccountInUse)
1117 | Err(TransactionError::AccountLoadedTwice)
1119 | Err(TransactionError::TooManyAccountLocks)
1120 | Err(TransactionError::CommitCancelled) => {
1122 unreachable!()
1123 }
1124 Err(error) => {
1125 error!("error is detected while accumulating....: {error:?}");
1126 *result = Err(error);
1127 true
1128 }
1129 }
1130 }
1131
1132 fn take_session_result_with_timings(&mut self) -> ResultWithTimings {
1133 self.session_result_with_timings.take().unwrap()
1134 }
1135
1136 fn put_session_result_with_timings(&mut self, result_with_timings: ResultWithTimings) {
1137 self.session_result_with_timings
1138 .replace(result_with_timings)
1139 .unwrap_none();
1140 }
1141
1142 fn start_threads(
1147 &mut self,
1148 context: SchedulingContext,
1149 mut result_with_timings: ResultWithTimings,
1150 handler_context: HandlerContext,
1151 ) {
1152 let mut current_slot = context.slot();
1153 let (mut is_finished, mut session_ending) = (false, false);
1154
1155 let (mut runnable_task_sender, runnable_task_receiver) =
1232 chained_channel::unbounded::<Task, SchedulingContext>(context);
1233 let (finished_blocked_task_sender, finished_blocked_task_receiver) =
1238 crossbeam_channel::unbounded::<HandlerResult>();
1239 let (finished_idle_task_sender, finished_idle_task_receiver) =
1240 crossbeam_channel::unbounded::<HandlerResult>();
1241
1242 assert_matches!(self.session_result_with_timings, None);
1243
1244 let scheduler_main_loop = {
1252 let handler_context = handler_context.clone_for_scheduler_thread();
1253 let session_result_sender = self.session_result_sender.clone();
1254 let new_task_receiver = self
1258 .new_task_receiver
1259 .take()
1260 .expect("no 2nd start_threads()");
1261
1262 move || {
1301 let (do_now, dont_now) = (&disconnected::<()>(), &never::<()>());
1302 let dummy_receiver = |trigger| {
1303 if trigger { do_now } else { dont_now }
1304 };
1305
1306 let mut state_machine = unsafe {
1307 SchedulingStateMachine::exclusively_initialize_current_thread_for_scheduling(
1308 Self::max_running_task_count(),
1309 Self::max_unique_active_task_count(),
1310 )
1311 };
1312
1313 'nonaborted_main_loop: loop {
1320 while !is_finished {
1321 let dummy_unblocked_task_receiver =
1326 dummy_receiver(Self::can_receive_unblocked_task(&state_machine));
1327
1328 select_biased! {
1339 recv(finished_blocked_task_receiver) -> receiver_result => {
1340 let handler_result = receiver_result.expect("alive handler");
1341 let Ok(executed_task) = handler_result else {
1342 break 'nonaborted_main_loop;
1343 };
1344 state_machine.deschedule_task(&executed_task.task);
1345
1346 if Self::abort_or_accumulate_result_with_timings(
1347 &mut result_with_timings,
1348 executed_task,
1349 ) {
1350 break 'nonaborted_main_loop;
1351 }
1352 },
1353 recv(dummy_unblocked_task_receiver) -> dummy => {
1354 assert_matches!(dummy, Err(RecvError));
1355
1356 let task = state_machine
1357 .schedule_next_unblocked_task()
1358 .expect("unblocked task");
1359 runnable_task_sender.send_payload(task).unwrap();
1360 },
1361 recv(new_task_receiver) -> message => {
1362 assert!(!session_ending);
1363
1364 match message {
1365 Ok(NewTaskPayload::Payload(task)) => {
1366 let task_id = task.task_id();
1367 sleepless_testing::at(CheckPoint::NewTask(task_id));
1368
1369 if let Some(task) = state_machine.schedule_or_buffer_task(task, session_ending) {
1370 runnable_task_sender.send_aux_payload(task).unwrap();
1371 } else {
1372 sleepless_testing::at(CheckPoint::BufferedOrDroppedTask(task_id));
1373 }
1374 }
1375 Ok(NewTaskPayload::CloseSubchannel) => {
1376 sleepless_testing::at(CheckPoint::SessionEnding);
1377 session_ending = true;
1378 }
1379 Ok(
1380 NewTaskPayload::OpenSubchannel(_)
1381 | NewTaskPayload::UnpauseOpenedSubchannel
1382 | NewTaskPayload::Reset
1383 )
1384 | Err(RecvError) => unreachable!(),
1385 Ok(NewTaskPayload::Disconnect) => {
1386 break 'nonaborted_main_loop;
1390 }
1391 }
1392 },
1393 recv(finished_idle_task_receiver) -> receiver_result => {
1394 let handler_result = receiver_result.expect("alive handler");
1395 let Ok(executed_task) = handler_result else {
1396 break 'nonaborted_main_loop;
1397 };
1398 state_machine.deschedule_task(&executed_task.task);
1399
1400 if Self::abort_or_accumulate_result_with_timings(
1401 &mut result_with_timings,
1402 executed_task,
1403 ) {
1404 break 'nonaborted_main_loop;
1405 }
1406 },
1407 };
1408
1409 is_finished = Self::can_finish_session(session_ending, &state_machine);
1410 }
1411 assert!(mem::replace(&mut is_finished, false));
1412
1413 sleepless_testing::at(CheckPoint::SessionFinished(current_slot));
1414 session_result_sender
1417 .send(result_with_timings)
1418 .expect("always outlived receiver");
1419
1420 state_machine.reinitialize();
1421 assert!(mem::replace(&mut session_ending, false));
1422
1423 let mut new_result_with_timings = None;
1429
1430 let discard_on_reset = false;
1431 #[allow(clippy::never_loop)]
1432 loop {
1433 if discard_on_reset {
1434 let count = state_machine.clear_and_reinitialize();
1442 sleepless_testing::at(CheckPoint::Discarded(count));
1443 }
1444 match new_task_receiver.recv() {
1446 Ok(NewTaskPayload::Payload(_task)) => {
1447 unreachable!("cannot receive new task before session start");
1448 }
1449 Ok(NewTaskPayload::OpenSubchannel(context_and_result_with_timings)) => {
1450 let new_context = context_and_result_with_timings.0;
1451 new_result_with_timings
1452 .replace(context_and_result_with_timings.1)
1453 .unwrap_none();
1454 current_slot = new_context.slot();
1458 runnable_task_sender
1459 .send_chained_channel(
1460 &new_context,
1461 handler_context.thread_count,
1462 )
1463 .unwrap();
1464
1465 break;
1466 }
1467 Ok(NewTaskPayload::UnpauseOpenedSubchannel) => {
1468 unreachable!("unpause without open is prohibited");
1469 }
1470 Ok(NewTaskPayload::CloseSubchannel) => {
1471 unreachable!("close without open is prohibited");
1472 }
1473 Ok(NewTaskPayload::Reset) => {
1474 unreachable!("reset without open is prohibited");
1475 }
1476 Ok(NewTaskPayload::Disconnect) => {
1477 result_with_timings = initialized_result_with_timings();
1480 break 'nonaborted_main_loop;
1481 }
1482 Err(RecvError) => unreachable!(),
1483 }
1484 }
1485 result_with_timings = new_result_with_timings.unwrap();
1486 }
1487
1488 session_result_sender
1498 .send(result_with_timings)
1499 .expect("always outlived receiver");
1500
1501 drop(new_task_receiver);
1505
1506 sleepless_testing::at(CheckPoint::SchedulerThreadAborted);
1508 }
1509 };
1510
1511 let handler_main_loop = || {
1512 let handler_context = handler_context.clone();
1513 let mut runnable_task_receiver = runnable_task_receiver.clone();
1514 let finished_blocked_task_sender = finished_blocked_task_sender.clone();
1515 let finished_idle_task_sender = finished_idle_task_sender.clone();
1516
1517 move || {
1526 loop {
1527 let (task, sender) = select_biased! {
1528 recv(runnable_task_receiver.for_select()) -> message => {
1529 let Ok(message) = message else {
1530 break;
1531 };
1532 if let Some(task) = runnable_task_receiver.after_select(message) {
1533 (task, &finished_blocked_task_sender)
1534 } else {
1535 continue;
1536 }
1537 },
1538 recv(runnable_task_receiver.aux_for_select()) -> task => {
1539 if let Ok(task) = task {
1540 (task, &finished_idle_task_sender)
1541 } else {
1542 runnable_task_receiver.never_receive_from_aux();
1543 continue;
1544 }
1545 }
1546 };
1547 defer! {
1548 if !thread::panicking() {
1549 return;
1550 }
1551
1552 let current_thread = thread::current();
1556 error!("handler thread is panicking: {:?}", current_thread);
1557 if sender.send(Err(HandlerPanicked)).is_ok() {
1558 info!("notified a panic from {current_thread:?}");
1559 } else {
1560 warn!("failed to notify a panic from {current_thread:?}");
1562 }
1563 }
1564 let mut task = ExecutedTask::new_boxed(task);
1565 Self::execute_task_with_handler(
1566 runnable_task_receiver.context(),
1567 &mut task,
1568 &handler_context,
1569 );
1570 if sender.send(Ok(task)).is_err() {
1571 warn!("handler_thread: scheduler thread aborted...");
1572 break;
1573 }
1574 }
1575 }
1576 };
1577
1578 let mode_char = 'V';
1579
1580 self.scheduler_thread = Some(
1581 thread::Builder::new()
1582 .name(format!("solSchedule{mode_char}"))
1583 .spawn_tracked(scheduler_main_loop)
1584 .unwrap(),
1585 );
1586
1587 self.handler_threads = (0..handler_context.thread_count)
1588 .map({
1589 |thx| {
1590 thread::Builder::new()
1591 .name(format!("solScHandle{mode_char}{thx:02}"))
1592 .spawn_tracked(handler_main_loop())
1593 .unwrap()
1594 }
1595 })
1596 .collect();
1597 }
1598
1599 fn send_task(&self, task: Task) -> ScheduleResult {
1600 debug!("send_task()");
1601 self.new_task_sender
1602 .send(NewTaskPayload::Payload(task))
1603 .map_err(|_| SchedulerAborted)
1604 }
1605
1606 fn ensure_join_threads(&mut self, should_receive_session_result: bool) {
1607 trace!("ensure_join_threads() is called");
1608
1609 fn join_with_panic_message(join_handle: JoinHandle<()>) -> thread::Result<()> {
1610 let thread = join_handle.thread().clone();
1611 join_handle.join().inspect_err(|e| {
1612 let panic_message = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
1618 (Some(&s), _) => s,
1619 (_, Some(s)) => s,
1620 (None, None) => "<No panic info>",
1621 };
1622 panic!("{panic_message} (From: {thread:?})");
1623 })
1624 }
1625
1626 if let Some(scheduler_thread) = self.scheduler_thread.take() {
1627 for thread in self.handler_threads.drain(..) {
1628 debug!("joining...: {thread:?}");
1629 () = join_with_panic_message(thread).unwrap();
1630 }
1631 () = join_with_panic_message(scheduler_thread).unwrap();
1632
1633 if should_receive_session_result {
1634 let result_with_timings = self.session_result_receiver.recv().unwrap();
1635 debug!("ensure_join_threads(): err: {:?}", result_with_timings.0);
1636 self.put_session_result_with_timings(result_with_timings);
1637 }
1638 } else {
1639 warn!("ensure_join_threads(): skipping; already joined...");
1640 };
1641 }
1642
1643 fn ensure_join_threads_after_abort(
1644 &mut self,
1645 should_receive_aborted_session_result: bool,
1646 ) -> TransactionError {
1647 self.ensure_join_threads(should_receive_aborted_session_result);
1648 self.session_result_with_timings
1649 .as_mut()
1650 .unwrap()
1651 .0
1652 .clone()
1653 .unwrap_err()
1654 }
1655
1656 fn are_threads_joined(&self) -> bool {
1657 if self.scheduler_thread.is_none() {
1658 assert!(self.handler_threads.is_empty());
1661 true
1662 } else {
1663 false
1664 }
1665 }
1666
1667 fn end_session(&mut self) {
1668 self.do_end_session(false)
1669 }
1670
1671 fn do_end_session(&mut self, nonblocking: bool) {
1672 if self.are_threads_joined() {
1673 assert!(self.session_result_with_timings.is_some());
1674 debug!("end_session(): skipping; already joined the aborted threads...");
1675 return;
1676 } else if self.session_result_with_timings.is_some() {
1677 debug!("end_session(): skipping; already result resides within thread manager...");
1678 return;
1679 }
1680 debug!(
1681 "end_session(): will end session at {:?}...",
1682 thread::current(),
1683 );
1684
1685 let mut abort_detected = self
1686 .new_task_sender
1687 .send(NewTaskPayload::CloseSubchannel)
1688 .is_err();
1689
1690 if nonblocking {
1691 return;
1701 }
1702
1703 if abort_detected {
1704 self.ensure_join_threads_after_abort(true);
1705 return;
1706 }
1707
1708 let result_with_timings = self.session_result_receiver.recv().unwrap();
1711 abort_detected = result_with_timings.0.is_err();
1712 self.put_session_result_with_timings(result_with_timings);
1713 if abort_detected {
1714 self.ensure_join_threads_after_abort(false);
1715 }
1716 debug!("end_session(): ended session at {:?}...", thread::current());
1717 }
1718
1719 fn unpause_started_session(&self) {
1720 self.new_task_sender
1721 .send(NewTaskPayload::UnpauseOpenedSubchannel)
1722 .unwrap();
1723 }
1724
1725 fn start_session(
1726 &mut self,
1727 context: SchedulingContext,
1728 result_with_timings: ResultWithTimings,
1729 ) {
1730 assert!(!self.are_threads_joined());
1731 assert_matches!(self.session_result_with_timings, None);
1732 self.new_task_sender
1733 .send(NewTaskPayload::OpenSubchannel(Box::new((
1734 context,
1735 result_with_timings,
1736 ))))
1737 .expect("no new session after aborted");
1738 }
1739
1740 fn discard_buffered_tasks(&self) {
1741 self.new_task_sender.send(NewTaskPayload::Reset).unwrap();
1742 }
1743
1744 #[must_use]
1745 fn disconnect_new_task_sender(&mut self) -> bool {
1746 self.new_task_sender
1752 .send(NewTaskPayload::Disconnect)
1753 .is_err()
1754 }
1755}
1756
1757pub trait SchedulerInner {
1758 fn id(&self) -> SchedulerId;
1759 fn is_trashed(&self) -> bool;
1760 fn is_overgrown(&self) -> bool;
1761 fn discard_buffer(&self);
1762}
1763
1764pub trait SpawnableScheduler<TH: TaskHandler>: InstalledScheduler {
1765 type Inner: SchedulerInner + Debug + Send + Sync;
1766
1767 fn into_inner(self) -> (ResultWithTimings, Self::Inner);
1768
1769 fn from_inner(
1770 inner: Self::Inner,
1771 context: SchedulingContext,
1772 result_with_timings: ResultWithTimings,
1773 ) -> Self;
1774
1775 fn spawn(
1776 pool: Arc<SchedulerPool<Self, TH>>,
1777 context: SchedulingContext,
1778 result_with_timings: ResultWithTimings,
1779 ) -> Self
1780 where
1781 Self: Sized;
1782}
1783
1784impl<TH: TaskHandler> SpawnableScheduler<TH> for PooledScheduler<TH> {
1785 type Inner = PooledSchedulerInner<Self, TH>;
1786
1787 fn into_inner(mut self) -> (ResultWithTimings, Self::Inner) {
1788 let result_with_timings = {
1789 let manager = &mut self.inner.thread_manager;
1790 manager.end_session();
1791 manager.take_session_result_with_timings()
1792 };
1793 (result_with_timings, self.inner)
1794 }
1795
1796 fn from_inner(
1797 mut inner: Self::Inner,
1798 context: SchedulingContext,
1799 result_with_timings: ResultWithTimings,
1800 ) -> Self {
1801 inner
1802 .thread_manager
1803 .start_session(context.clone(), result_with_timings);
1804 Self { inner, context }
1805 }
1806
1807 fn spawn(
1808 pool: Arc<SchedulerPool<Self, TH>>,
1809 context: SchedulingContext,
1810 result_with_timings: ResultWithTimings,
1811 ) -> Self {
1812 let mut thread_manager = ThreadManager::new(pool.clone());
1813 let handler_context = pool.create_handler_context();
1814 let usage_queue_loader = handler_context.usage_queue_loader_for_newly_spawned();
1815 thread_manager.start_threads(context.clone(), result_with_timings, handler_context);
1816 let inner = Self::Inner {
1817 thread_manager,
1818 usage_queue_loader,
1819 };
1820 Self { inner, context }
1821 }
1822}
1823
1824impl<TH: TaskHandler> InstalledScheduler for PooledScheduler<TH> {
1825 fn id(&self) -> SchedulerId {
1826 self.inner.id()
1827 }
1828
1829 fn context(&self) -> &SchedulingContext {
1830 &self.context
1831 }
1832
1833 fn schedule_execution(
1834 &self,
1835 transaction: RuntimeTransaction<SanitizedTransaction>,
1836 task_id: OrderedTaskId,
1837 ) -> ScheduleResult {
1838 let task = SchedulingStateMachine::create_task(transaction, task_id, &mut |pubkey| {
1839 self.inner.usage_queue_loader.load(pubkey)
1840 });
1841 self.inner.thread_manager.send_task(task)
1842 }
1843
1844 fn recover_error_after_abort(&mut self) -> TransactionError {
1845 self.inner
1846 .thread_manager
1847 .ensure_join_threads_after_abort(true)
1848 }
1849
1850 fn wait_for_termination(
1851 self: Box<Self>,
1852 _is_dropped: bool,
1853 ) -> (ResultWithTimings, UninstalledSchedulerBox) {
1854 let (result_with_timings, uninstalled_scheduler) = self.into_inner();
1855 (result_with_timings, Box::new(uninstalled_scheduler))
1856 }
1857
1858 fn pause_for_recent_blockhash(&mut self) {
1859 self.inner.thread_manager.do_end_session(false);
1860 }
1861
1862 fn unpause_after_taken(&self) {
1863 self.inner.thread_manager.unpause_started_session();
1864 }
1865}
1866
1867impl<S, TH> SchedulerInner for PooledSchedulerInner<S, TH>
1868where
1869 S: SpawnableScheduler<TH>,
1870 TH: TaskHandler,
1871{
1872 fn id(&self) -> SchedulerId {
1873 self.thread_manager.scheduler_id
1874 }
1875
1876 fn is_trashed(&self) -> bool {
1877 self.is_aborted() || self.is_overgrown()
1878 }
1879
1880 fn is_overgrown(&self) -> bool {
1881 self.usage_queue_loader
1882 .is_overgrown(self.thread_manager.pool.max_usage_queue_count)
1883 }
1884
1885 fn discard_buffer(&self) {
1886 self.thread_manager.discard_buffered_tasks();
1887 }
1888}
1889
1890impl<S, TH> UninstalledScheduler for PooledSchedulerInner<S, TH>
1891where
1892 S: SpawnableScheduler<TH, Inner = Self>,
1893 TH: TaskHandler,
1894{
1895 fn return_to_pool(self: Box<Self>) {
1896 self.thread_manager.pool.clone().return_scheduler(*self);
1897 }
1898}
1899
1900#[cfg(test)]
1901mod tests {
1902 use {
1903 super::*,
1904 crate::sleepless_testing,
1905 assert_matches::assert_matches,
1906 solana_clock::Slot,
1907 solana_keypair::Keypair,
1908 solana_pubkey::Pubkey,
1909 solana_runtime::{
1910 bank::{Bank, SlotLeader},
1911 bank_forks::BankForks,
1912 genesis_utils::{GenesisConfigInfo, create_genesis_config},
1913 installed_scheduler_pool::{
1914 BankWithScheduler, InstalledSchedulerPoolArc, SchedulingContext,
1915 },
1916 },
1917 solana_svm_timings::ExecuteTimingType,
1918 solana_system_transaction as system_transaction,
1919 solana_transaction::sanitized::SanitizedTransaction,
1920 solana_transaction_error::TransactionError,
1921 std::{
1922 num::Saturating,
1923 sync::{Arc, RwLock},
1924 thread::JoinHandle,
1925 },
1926 };
1927
1928 impl<S, TH> SchedulerPool<S, TH>
1929 where
1930 S: SpawnableScheduler<TH>,
1931 TH: TaskHandler,
1932 {
1933 fn do_new_for_verification(
1934 block_verification_handler_count: CountOrDefault,
1935 log_messages_bytes_limit: Option<usize>,
1936 transaction_status_sender: Option<TransactionStatusSender>,
1937 replay_vote_sender: Option<ReplayVoteSender>,
1938 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
1939 pool_cleaner_interval: Duration,
1940 max_pooling_duration: Duration,
1941 max_usage_queue_count: usize,
1942 timeout_duration: Duration,
1943 ) -> Arc<Self> {
1944 Self::do_new(
1945 block_verification_handler_count,
1946 log_messages_bytes_limit,
1947 transaction_status_sender,
1948 replay_vote_sender,
1949 prioritization_fee_cache,
1950 pool_cleaner_interval,
1951 max_pooling_duration,
1952 max_usage_queue_count,
1953 timeout_duration,
1954 )
1955 }
1956
1957 fn new_dyn_for_verification(
1960 block_verification_handler_count: CountOrDefault,
1961 log_messages_bytes_limit: Option<usize>,
1962 transaction_status_sender: Option<TransactionStatusSender>,
1963 replay_vote_sender: Option<ReplayVoteSender>,
1964 prioritization_fee_cache: Option<Arc<PrioritizationFeeCache>>,
1965 ) -> InstalledSchedulerPoolArc {
1966 Self::new(
1967 block_verification_handler_count,
1968 log_messages_bytes_limit,
1969 transaction_status_sender,
1970 replay_vote_sender,
1971 prioritization_fee_cache,
1972 )
1973 }
1974 }
1975
1976 #[derive(Debug)]
1977 enum TestCheckPoint {
1978 BeforeNewTask,
1979 AfterBufferedTask,
1980 AfterTaskHandled,
1981 AfterSessionEnding,
1982 AfterSchedulerThreadAborted,
1983 BeforeIdleSchedulerCleaned,
1984 AfterIdleSchedulerCleaned,
1985 BeforeTrashedSchedulerCleaned,
1986 AfterTrashedSchedulerCleaned,
1987 BeforeTimeoutListenerTriggered,
1988 AfterTimeoutListenerTriggered,
1989 BeforeThreadManagerDrop,
1990 BeforeEndSession,
1991 }
1992
1993 #[test]
1994 fn test_scheduler_pool_new() {
1995 agave_logger::setup();
1996
1997 let pool = DefaultSchedulerPool::new_dyn_for_verification(None, None, None, None, None);
1998
1999 assert_eq!((Arc::strong_count(&pool), Arc::weak_count(&pool)), (1, 2));
2003 let debug = format!("{pool:#?}");
2004 assert!(!debug.is_empty());
2005 }
2006
2007 #[test]
2008 fn test_scheduler_spawn() {
2009 agave_logger::setup();
2010
2011 let pool = DefaultSchedulerPool::new_dyn_for_verification(None, None, None, None, None);
2012 let bank = Arc::new(Bank::default_for_tests());
2013 let context = SchedulingContext::new(bank);
2014 let scheduler = pool.take_scheduler(context).unwrap();
2015
2016 let debug = format!("{scheduler:#?}");
2017 assert!(!debug.is_empty());
2018 }
2019
2020 const SHORTENED_POOL_CLEANER_INTERVAL: Duration = Duration::from_millis(1);
2021
2022 #[test]
2023 fn test_scheduler_drop_idle() {
2024 agave_logger::setup();
2025
2026 let _progress = sleepless_testing::setup(&[
2027 &TestCheckPoint::BeforeIdleSchedulerCleaned,
2028 &CheckPoint::IdleSchedulerCleaned(0),
2029 &CheckPoint::IdleSchedulerCleaned(1),
2030 &TestCheckPoint::AfterIdleSchedulerCleaned,
2031 ]);
2032
2033 const TEST_MAX_POOLING_DURATION_MS: u64 = 300;
2038 const TEST_MAX_POOLING_DURATION: Duration =
2039 Duration::from_millis(TEST_MAX_POOLING_DURATION_MS);
2040 const TEST_WAIT_FOR_IDLE_MS: u64 = TEST_MAX_POOLING_DURATION_MS + 200;
2041 const TEST_WAIT_FOR_IDLE: Duration = Duration::from_millis(TEST_WAIT_FOR_IDLE_MS);
2042 let pool_raw = DefaultSchedulerPool::do_new_for_verification(
2043 None,
2044 None,
2045 None,
2046 None,
2047 None,
2048 SHORTENED_POOL_CLEANER_INTERVAL,
2049 TEST_MAX_POOLING_DURATION,
2050 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2051 DEFAULT_TIMEOUT_DURATION,
2052 );
2053 let pool = pool_raw.clone();
2054 let bank = Arc::new(Bank::default_for_tests());
2055 let context1 = SchedulingContext::new(bank);
2056 let context2 = context1.clone();
2057
2058 let old_scheduler = pool.do_take_scheduler(context1);
2059 let new_scheduler = pool.do_take_scheduler(context2);
2060 let new_scheduler_id = new_scheduler.id();
2061 Box::new(old_scheduler.into_inner().1).return_to_pool();
2062
2063 sleep(TEST_WAIT_FOR_IDLE);
2065
2066 Box::new(new_scheduler.into_inner().1).return_to_pool();
2067
2068 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 2);
2070 sleepless_testing::at(TestCheckPoint::BeforeIdleSchedulerCleaned);
2071
2072 sleepless_testing::at(&TestCheckPoint::AfterIdleSchedulerCleaned);
2074
2075 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
2078 assert_eq!(
2079 pool_raw
2080 .scheduler_inners
2081 .lock()
2082 .unwrap()
2083 .first()
2084 .as_ref()
2085 .map(|(inner, _pooled_at)| inner.id())
2086 .unwrap(),
2087 new_scheduler_id
2088 );
2089 }
2090
2091 #[test]
2092 fn test_scheduler_drop_overgrown() {
2093 agave_logger::setup();
2094
2095 let _progress = sleepless_testing::setup(&[
2096 &TestCheckPoint::BeforeTrashedSchedulerCleaned,
2097 &CheckPoint::TrashedSchedulerCleaned(0),
2098 &CheckPoint::TrashedSchedulerCleaned(1),
2099 &TestCheckPoint::AfterTrashedSchedulerCleaned,
2100 ]);
2101
2102 const REDUCED_MAX_USAGE_QUEUE_COUNT: usize = 1;
2103 let pool_raw = DefaultSchedulerPool::do_new_for_verification(
2104 None,
2105 None,
2106 None,
2107 None,
2108 None,
2109 SHORTENED_POOL_CLEANER_INTERVAL,
2110 DEFAULT_MAX_POOLING_DURATION,
2111 REDUCED_MAX_USAGE_QUEUE_COUNT,
2112 DEFAULT_TIMEOUT_DURATION,
2113 );
2114 let pool = pool_raw.clone();
2115 let bank = Arc::new(Bank::default_for_tests());
2116 let context1 = SchedulingContext::new(bank);
2117 let context2 = context1.clone();
2118
2119 let small_scheduler = pool.do_take_scheduler(context1);
2120 let small_scheduler_id = small_scheduler.id();
2121 for _ in 0..REDUCED_MAX_USAGE_QUEUE_COUNT {
2122 small_scheduler
2123 .inner
2124 .usage_queue_loader
2125 .load(Pubkey::new_unique());
2126 }
2127 let big_scheduler = pool.do_take_scheduler(context2);
2128 for _ in 0..REDUCED_MAX_USAGE_QUEUE_COUNT + 1 {
2129 big_scheduler
2130 .inner
2131 .usage_queue_loader
2132 .load(Pubkey::new_unique());
2133 }
2134
2135 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0);
2136 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2137 Box::new(small_scheduler.into_inner().1).return_to_pool();
2138 Box::new(big_scheduler.into_inner().1).return_to_pool();
2139
2140 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
2142 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 1);
2143 sleepless_testing::at(TestCheckPoint::BeforeTrashedSchedulerCleaned);
2144
2145 sleepless_testing::at(&TestCheckPoint::AfterTrashedSchedulerCleaned);
2147 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
2148 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2149 assert_eq!(
2150 pool_raw
2151 .scheduler_inners
2152 .lock()
2153 .unwrap()
2154 .first()
2155 .as_ref()
2156 .map(|(inner, _pooled_at)| inner.id())
2157 .unwrap(),
2158 small_scheduler_id
2159 );
2160 }
2161
2162 const SHORTENED_TIMEOUT_DURATION: Duration = Duration::from_millis(1);
2163
2164 #[test]
2165 fn test_scheduler_drop_stale() {
2166 const SHORTENED_MAX_POOLING_DURATION: Duration = Duration::from_millis(100);
2167 agave_logger::setup();
2168
2169 let _progress = sleepless_testing::setup(&[
2170 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2171 &CheckPoint::TimeoutListenerTriggered(0),
2172 &CheckPoint::TimeoutListenerTriggered(1),
2173 &TestCheckPoint::AfterTimeoutListenerTriggered,
2174 &CheckPoint::IdleSchedulerCleaned(1),
2175 &TestCheckPoint::AfterIdleSchedulerCleaned,
2176 ]);
2177
2178 let pool_raw = DefaultSchedulerPool::do_new_for_verification(
2179 None,
2180 None,
2181 None,
2182 None,
2183 None,
2184 SHORTENED_POOL_CLEANER_INTERVAL,
2185 SHORTENED_MAX_POOLING_DURATION,
2186 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2187 SHORTENED_TIMEOUT_DURATION,
2188 );
2189 let pool = pool_raw.clone();
2190 let bank = Arc::new(Bank::default_for_tests());
2191 let context = SchedulingContext::new(bank.clone());
2192 let scheduler = pool.take_scheduler(context).unwrap();
2193 let bank = BankWithScheduler::new(bank, Some(scheduler));
2194 pool.register_timeout_listener(bank.create_timeout_listener());
2195 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0);
2196 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2197 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2198
2199 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2200 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1);
2201 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2202 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
2203
2204 sleepless_testing::at(&TestCheckPoint::AfterIdleSchedulerCleaned);
2206 assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0);
2207 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2208 }
2209
2210 #[test]
2211 fn test_scheduler_active_after_stale() {
2212 agave_logger::setup();
2213
2214 let _progress = sleepless_testing::setup(&[
2215 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2216 &CheckPoint::TimeoutListenerTriggered(0),
2217 &CheckPoint::TimeoutListenerTriggered(1),
2218 &TestCheckPoint::AfterTimeoutListenerTriggered,
2219 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2220 &CheckPoint::TimeoutListenerTriggered(0),
2221 &CheckPoint::TimeoutListenerTriggered(1),
2222 &TestCheckPoint::AfterTimeoutListenerTriggered,
2223 ]);
2224
2225 let pool_raw =
2226 SchedulerPool::<PooledScheduler<ExecuteTimingCounter>, _>::do_new_for_verification(
2227 None,
2228 None,
2229 None,
2230 None,
2231 None,
2232 SHORTENED_POOL_CLEANER_INTERVAL,
2233 DEFAULT_MAX_POOLING_DURATION,
2234 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2235 SHORTENED_TIMEOUT_DURATION,
2236 );
2237
2238 #[derive(Debug)]
2239 struct ExecuteTimingCounter;
2240 impl TaskHandler for ExecuteTimingCounter {
2241 fn handle(
2242 _result: &mut Result<()>,
2243 timings: &mut ExecuteTimings,
2244 _scheduling_context: &SchedulingContext,
2245 _task: &Task,
2246 _handler_context: &HandlerContext,
2247 ) {
2248 timings.metrics[ExecuteTimingType::CheckUs] += 123;
2249 }
2250 }
2251 let pool = pool_raw.clone();
2252
2253 let GenesisConfigInfo {
2254 genesis_config,
2255 mint_keypair,
2256 ..
2257 } = create_genesis_config(10_000);
2258 let bank = Bank::new_for_tests(&genesis_config);
2259 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2260
2261 let context = SchedulingContext::new(bank.clone());
2262
2263 let scheduler = pool.take_scheduler(context).unwrap();
2264 let bank = BankWithScheduler::new(bank, Some(scheduler));
2265 pool.register_timeout_listener(bank.create_timeout_listener());
2266
2267 let tx_before_stale =
2268 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2269 &mint_keypair,
2270 &solana_pubkey::new_rand(),
2271 2,
2272 genesis_config.hash(),
2273 ));
2274 bank.schedule_transaction_executions([(tx_before_stale, 0)].into_iter())
2275 .unwrap();
2276 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2277
2278 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2279 let tx_after_stale =
2280 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2281 &mint_keypair,
2282 &solana_pubkey::new_rand(),
2283 2,
2284 genesis_config.hash(),
2285 ));
2286 bank.schedule_transaction_executions([(tx_after_stale, 1)].into_iter())
2287 .unwrap();
2288
2289 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2292 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2293
2294 let (result, timings) = bank.wait_for_completed_scheduler().unwrap();
2295 assert_matches!(result, Ok(()));
2296 assert_eq!(timings.metrics[ExecuteTimingType::CheckUs].0, 246);
2298 }
2299
2300 #[test]
2301 fn test_scheduler_pause_after_stale() {
2302 agave_logger::setup();
2303
2304 let _progress = sleepless_testing::setup(&[
2305 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2306 &CheckPoint::TimeoutListenerTriggered(0),
2307 &CheckPoint::TimeoutListenerTriggered(1),
2308 &TestCheckPoint::AfterTimeoutListenerTriggered,
2309 ]);
2310
2311 let pool_raw = DefaultSchedulerPool::do_new_for_verification(
2312 None,
2313 None,
2314 None,
2315 None,
2316 None,
2317 SHORTENED_POOL_CLEANER_INTERVAL,
2318 DEFAULT_MAX_POOLING_DURATION,
2319 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2320 SHORTENED_TIMEOUT_DURATION,
2321 );
2322 let pool = pool_raw.clone();
2323
2324 let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
2325 let bank = Bank::new_for_tests(&genesis_config);
2326 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2327
2328 let context = SchedulingContext::new(bank.clone());
2329
2330 let scheduler = pool.take_scheduler(context).unwrap();
2331 let bank = BankWithScheduler::new(bank, Some(scheduler));
2332 pool.register_timeout_listener(bank.create_timeout_listener());
2333
2334 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2335 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2336
2337 bank.fill_bank_with_ticks_for_tests();
2340 let (result, _timings) = bank.wait_for_completed_scheduler().unwrap();
2341 assert_matches!(result, Ok(()));
2342 }
2343
2344 #[test]
2345 fn test_scheduler_remain_stale_after_error() {
2346 agave_logger::setup();
2347
2348 let _progress = sleepless_testing::setup(&[
2349 &TestCheckPoint::BeforeTimeoutListenerTriggered,
2350 &CheckPoint::TimeoutListenerTriggered(0),
2351 &CheckPoint::SchedulerThreadAborted,
2352 &TestCheckPoint::AfterSchedulerThreadAborted,
2353 &CheckPoint::TimeoutListenerTriggered(1),
2354 &TestCheckPoint::AfterTimeoutListenerTriggered,
2355 ]);
2356
2357 let pool_raw = SchedulerPool::<PooledScheduler<FaultyHandler>, _>::do_new_for_verification(
2358 None,
2359 None,
2360 None,
2361 None,
2362 None,
2363 SHORTENED_POOL_CLEANER_INTERVAL,
2364 DEFAULT_MAX_POOLING_DURATION,
2365 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2366 SHORTENED_TIMEOUT_DURATION,
2367 );
2368
2369 let pool = pool_raw.clone();
2370
2371 let GenesisConfigInfo {
2372 genesis_config,
2373 mint_keypair,
2374 ..
2375 } = create_genesis_config(10_000);
2376 let bank = Bank::new_for_tests(&genesis_config);
2377 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2378
2379 let context = SchedulingContext::new(bank.clone());
2380
2381 let scheduler = pool.take_scheduler(context).unwrap();
2382 let bank = BankWithScheduler::new(bank, Some(scheduler));
2383 pool.register_timeout_listener(bank.create_timeout_listener());
2384
2385 let tx_before_stale =
2386 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2387 &mint_keypair,
2388 &solana_pubkey::new_rand(),
2389 2,
2390 genesis_config.hash(),
2391 ));
2392 bank.schedule_transaction_executions([(tx_before_stale, 0)].into_iter())
2393 .unwrap();
2394 sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered);
2395 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2396
2397 sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered);
2398 let tx_after_stale =
2399 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2400 &mint_keypair,
2401 &solana_pubkey::new_rand(),
2402 2,
2403 genesis_config.hash(),
2404 ));
2405 let result = bank.schedule_transaction_executions([(tx_after_stale, 1)].into_iter());
2406 assert_matches!(result, Err(TransactionError::AccountNotFound));
2407
2408 let (result, _timings) = bank.wait_for_completed_scheduler().unwrap();
2409 assert_matches!(result, Err(TransactionError::AccountNotFound));
2410 }
2411
2412 enum AbortCase {
2413 Unhandled,
2414 UnhandledWhilePanicking,
2415 Handled,
2416 }
2417
2418 #[derive(Debug)]
2419 struct FaultyHandler;
2420 impl TaskHandler for FaultyHandler {
2421 fn handle(
2422 result: &mut Result<()>,
2423 _timings: &mut ExecuteTimings,
2424 _scheduling_context: &SchedulingContext,
2425 _task: &Task,
2426 _handler_context: &HandlerContext,
2427 ) {
2428 *result = Err(TransactionError::AccountNotFound);
2429 }
2430 }
2431
2432 fn do_test_scheduler_drop_abort(abort_case: AbortCase) {
2433 agave_logger::setup();
2434
2435 let _progress = sleepless_testing::setup(match abort_case {
2436 AbortCase::Unhandled => &[
2437 &CheckPoint::SchedulerThreadAborted,
2438 &TestCheckPoint::AfterSchedulerThreadAborted,
2439 ],
2440 _ => &[],
2441 });
2442
2443 let GenesisConfigInfo {
2444 genesis_config,
2445 mint_keypair,
2446 ..
2447 } = create_genesis_config(10_000);
2448
2449 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2450 &mint_keypair,
2451 &solana_pubkey::new_rand(),
2452 2,
2453 genesis_config.hash(),
2454 ));
2455
2456 let bank = Bank::new_for_tests(&genesis_config);
2457 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2458 let pool = SchedulerPool::<PooledScheduler<FaultyHandler>, _>::new_for_verification(
2459 None, None, None, None, None,
2460 );
2461 let context = SchedulingContext::new(bank.clone());
2462 let scheduler = pool.do_take_scheduler(context);
2463 scheduler.schedule_execution(tx, 0).unwrap();
2464
2465 match abort_case {
2466 AbortCase::Unhandled => {
2467 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2468 drop::<PooledScheduler<_>>(scheduler);
2472 }
2473 AbortCase::UnhandledWhilePanicking => {
2474 panic!("ThreadManager::drop() should be skipped...");
2476 }
2477 AbortCase::Handled => {
2478 let ((result, _), mut scheduler_inner) = scheduler.into_inner();
2480 assert_matches!(result, Err(TransactionError::AccountNotFound));
2481
2482 let dummy_flag = true; scheduler_inner
2485 .thread_manager
2486 .ensure_join_threads(dummy_flag);
2487
2488 drop::<PooledSchedulerInner<_, _>>(scheduler_inner);
2489 }
2490 }
2491 }
2492
2493 #[test]
2494 #[should_panic(expected = "does not match `Some((Ok(_), _))")]
2495 fn test_scheduler_drop_abort_unhandled() {
2496 do_test_scheduler_drop_abort(AbortCase::Unhandled);
2497 }
2498
2499 #[test]
2500 #[should_panic(expected = "ThreadManager::drop() should be skipped...")]
2501 fn test_scheduler_drop_abort_unhandled_while_panicking() {
2502 do_test_scheduler_drop_abort(AbortCase::UnhandledWhilePanicking);
2503 }
2504
2505 #[test]
2506 fn test_scheduler_drop_abort_handled() {
2507 do_test_scheduler_drop_abort(AbortCase::Handled);
2508 }
2509
2510 #[test]
2511 fn test_scheduler_drop_short_circuiting() {
2512 agave_logger::setup();
2513
2514 let _progress = sleepless_testing::setup(&[
2515 &TestCheckPoint::BeforeThreadManagerDrop,
2516 &CheckPoint::NewTask(0),
2517 &CheckPoint::SchedulerThreadAborted,
2518 &TestCheckPoint::AfterSchedulerThreadAborted,
2519 ]);
2520
2521 static TASK_COUNT: Mutex<OrderedTaskId> = Mutex::new(0);
2522
2523 #[derive(Debug)]
2524 struct CountingHandler;
2525 impl TaskHandler for CountingHandler {
2526 fn handle(
2527 _result: &mut Result<()>,
2528 _timings: &mut ExecuteTimings,
2529 _scheduling_context: &SchedulingContext,
2530 _task: &Task,
2531 _handler_context: &HandlerContext,
2532 ) {
2533 *TASK_COUNT.lock().unwrap() += 1;
2534 }
2535 }
2536
2537 let GenesisConfigInfo {
2538 genesis_config,
2539 mint_keypair,
2540 ..
2541 } = create_genesis_config(10_000);
2542
2543 let bank = Bank::new_for_tests(&genesis_config);
2544 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2545 let pool = SchedulerPool::<PooledScheduler<CountingHandler>, _>::new_for_verification(
2546 None, None, None, None, None,
2547 );
2548 let context = SchedulingContext::new(bank.clone());
2549 let scheduler = pool.do_take_scheduler(context);
2550
2551 const MAX_TASK_COUNT: OrderedTaskId = 100;
2557
2558 for i in 0..MAX_TASK_COUNT {
2559 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2560 &mint_keypair,
2561 &solana_pubkey::new_rand(),
2562 2,
2563 genesis_config.hash(),
2564 ));
2565 scheduler.schedule_execution(tx, i).unwrap();
2566 }
2567
2568 sleepless_testing::at(TestCheckPoint::BeforeThreadManagerDrop);
2570 drop::<PooledScheduler<_>>(scheduler);
2571 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2572 assert!(*TASK_COUNT.lock().unwrap() < MAX_TASK_COUNT);
2574 }
2575
2576 #[test]
2577 fn test_scheduler_pool_filo() {
2578 agave_logger::setup();
2579
2580 let pool = DefaultSchedulerPool::new_for_verification(None, None, None, None, None);
2581 let bank = Arc::new(Bank::default_for_tests());
2582 let context = &SchedulingContext::new(bank);
2583
2584 let scheduler1 = pool.do_take_scheduler(context.clone());
2585 let scheduler_id1 = scheduler1.id();
2586 let scheduler2 = pool.do_take_scheduler(context.clone());
2587 let scheduler_id2 = scheduler2.id();
2588 assert_ne!(scheduler_id1, scheduler_id2);
2589
2590 let (result_with_timings, scheduler1) = scheduler1.into_inner();
2591 assert_matches!(result_with_timings, (Ok(()), _));
2592 pool.return_scheduler(scheduler1);
2593 let (result_with_timings, scheduler2) = scheduler2.into_inner();
2594 assert_matches!(result_with_timings, (Ok(()), _));
2595 pool.return_scheduler(scheduler2);
2596
2597 let scheduler3 = pool.do_take_scheduler(context.clone());
2598 assert_eq!(scheduler_id2, scheduler3.id());
2599 let scheduler4 = pool.do_take_scheduler(context.clone());
2600 assert_eq!(scheduler_id1, scheduler4.id());
2601 }
2602
2603 #[test]
2604 fn test_scheduler_pool_context_drop_unless_reinitialized() {
2605 agave_logger::setup();
2606
2607 let pool = DefaultSchedulerPool::new_for_verification(None, None, None, None, None);
2608 let bank = Arc::new(Bank::default_for_tests());
2609 let context = &SchedulingContext::new(bank);
2610 let mut scheduler = pool.do_take_scheduler(context.clone());
2611
2612 scheduler.pause_for_recent_blockhash();
2614 assert_matches!(
2615 Box::new(scheduler).wait_for_termination(false),
2616 ((Ok(()), _), _)
2617 );
2618 }
2619
2620 #[test]
2621 fn test_scheduler_pool_context_replace() {
2622 agave_logger::setup();
2623
2624 let pool = DefaultSchedulerPool::new_for_verification(None, None, None, None, None);
2625 let old_bank = &Arc::new(Bank::default_for_tests());
2626 let new_bank = &Arc::new(Bank::default_for_tests());
2627 assert!(!Arc::ptr_eq(old_bank, new_bank));
2628
2629 let old_context = &SchedulingContext::new(old_bank.clone());
2630 let new_context = &SchedulingContext::new(new_bank.clone());
2631
2632 let scheduler = pool.do_take_scheduler(old_context.clone());
2633 let scheduler_id = scheduler.id();
2634 pool.return_scheduler(scheduler.into_inner().1);
2635
2636 let scheduler = pool.take_scheduler(new_context.clone()).unwrap();
2637 assert_eq!(scheduler_id, scheduler.id());
2638 assert!(Arc::ptr_eq(scheduler.context().bank(), new_bank));
2639 }
2640
2641 #[test]
2642 fn test_scheduler_pool_install_into_bank_forks() {
2643 agave_logger::setup();
2644
2645 let bank = Bank::default_for_tests();
2646 let bank_forks = BankForks::new_rw_arc(bank);
2647 let mut bank_forks = bank_forks.write().unwrap();
2648 let pool = DefaultSchedulerPool::new_dyn_for_verification(None, None, None, None, None);
2649 bank_forks.install_scheduler_pool(pool);
2650 }
2651
2652 #[test]
2653 fn test_scheduler_install_into_bank() {
2654 agave_logger::setup();
2655
2656 let pool = DefaultSchedulerPool::new_dyn_for_verification(None, None, None, None, None);
2657
2658 let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
2659 let bank = Bank::new_for_tests(&genesis_config);
2660 let bank_forks = BankForks::new_rw_arc(bank);
2661 let bank = bank_forks.read().unwrap().root_bank();
2662
2663 {
2666 let mut bank_forks_w = bank_forks.write().unwrap();
2667 assert!(
2668 !bank_forks_w
2669 .working_bank_with_scheduler()
2670 .has_installed_scheduler()
2671 );
2672 bank_forks_w.install_scheduler_pool(pool);
2673 assert!(
2674 !bank_forks_w
2675 .working_bank_with_scheduler()
2676 .has_installed_scheduler()
2677 );
2678 }
2679
2680 Bank::new_from_parent_with_bank_forks(bank_forks.as_ref(), bank, SlotLeader::default(), 1);
2682 let mut bank_forks = bank_forks.write().unwrap();
2683 let mut child_bank = bank_forks.get_with_scheduler(1).unwrap();
2684 assert!(child_bank.has_installed_scheduler());
2685 bank_forks.remove(child_bank.slot());
2686 child_bank.drop_scheduler();
2687 assert!(!child_bank.has_installed_scheduler());
2688 }
2689
2690 fn setup_dummy_fork_graph(bank: Bank) -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
2691 let slot = bank.slot();
2692 let bank_fork = BankForks::new_rw_arc(bank);
2693 let bank = bank_fork.read().unwrap().get(slot).unwrap();
2694 bank.set_fork_graph_in_program_cache(Arc::downgrade(&bank_fork));
2695 (bank, bank_fork)
2696 }
2697
2698 #[test]
2699 fn test_scheduler_schedule_execution_success() {
2700 agave_logger::setup();
2701
2702 let GenesisConfigInfo {
2703 genesis_config,
2704 mint_keypair,
2705 ..
2706 } = create_genesis_config(10_000);
2707 let tx0 = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2708 &mint_keypair,
2709 &solana_pubkey::new_rand(),
2710 2,
2711 genesis_config.hash(),
2712 ));
2713 let bank = Bank::new_for_tests(&genesis_config);
2714 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2715 let pool = DefaultSchedulerPool::new_dyn_for_verification(None, None, None, None, None);
2716 let context = SchedulingContext::new(bank.clone());
2717
2718 assert_eq!(bank.transaction_count(), 0);
2719 let scheduler = pool.take_scheduler(context).unwrap();
2720 scheduler.schedule_execution(tx0, 0).unwrap();
2721 let bank = BankWithScheduler::new(bank, Some(scheduler));
2722 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
2723 assert_eq!(bank.transaction_count(), 1);
2724 }
2725
2726 fn do_test_scheduler_schedule_execution_failure(extra_tx_after_failure: bool) {
2727 agave_logger::setup();
2728
2729 let _progress = sleepless_testing::setup(&[
2730 &CheckPoint::TaskHandled(0),
2731 &TestCheckPoint::AfterTaskHandled,
2732 &CheckPoint::SchedulerThreadAborted,
2733 &TestCheckPoint::AfterSchedulerThreadAborted,
2734 &TestCheckPoint::BeforeTrashedSchedulerCleaned,
2735 &CheckPoint::TrashedSchedulerCleaned(0),
2736 &CheckPoint::TrashedSchedulerCleaned(1),
2737 &TestCheckPoint::AfterTrashedSchedulerCleaned,
2738 ]);
2739
2740 let GenesisConfigInfo {
2741 genesis_config,
2742 mint_keypair,
2743 ..
2744 } = create_genesis_config(10_000);
2745 let bank = Bank::new_for_tests(&genesis_config);
2746 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2747
2748 let pool_raw = DefaultSchedulerPool::do_new_for_verification(
2749 None,
2750 None,
2751 None,
2752 None,
2753 None,
2754 SHORTENED_POOL_CLEANER_INTERVAL,
2755 DEFAULT_MAX_POOLING_DURATION,
2756 DEFAULT_MAX_USAGE_QUEUE_COUNT,
2757 DEFAULT_TIMEOUT_DURATION,
2758 );
2759 let pool = pool_raw.clone();
2760 let context = SchedulingContext::new(bank.clone());
2761 let scheduler = pool.take_scheduler(context).unwrap();
2762
2763 let unfunded_keypair = Keypair::new();
2764 let bad_tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2765 &unfunded_keypair,
2766 &solana_pubkey::new_rand(),
2767 2,
2768 genesis_config.hash(),
2769 ));
2770 assert_eq!(bank.transaction_count(), 0);
2771 scheduler.schedule_execution(bad_tx, 0).unwrap();
2772 sleepless_testing::at(TestCheckPoint::AfterTaskHandled);
2773 assert_eq!(bank.transaction_count(), 0);
2774
2775 let good_tx_after_bad_tx =
2776 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2777 &mint_keypair,
2778 &solana_pubkey::new_rand(),
2779 3,
2780 genesis_config.hash(),
2781 ));
2782 assert_matches!(
2784 bank.simulate_transaction_unchecked(&good_tx_after_bad_tx, false)
2785 .result,
2786 Ok(_)
2787 );
2788 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2789 let bank = BankWithScheduler::new(bank, Some(scheduler));
2790 if extra_tx_after_failure {
2791 assert_matches!(
2792 bank.schedule_transaction_executions([(good_tx_after_bad_tx, 1)].into_iter()),
2793 Err(TransactionError::AccountNotFound)
2794 );
2795 }
2796 assert_eq!(bank.transaction_count(), 0);
2802
2803 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2804 assert_matches!(
2805 bank.wait_for_completed_scheduler(),
2806 Some((Err(TransactionError::AccountNotFound), _timings))
2807 );
2808
2809 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 1);
2811 sleepless_testing::at(TestCheckPoint::BeforeTrashedSchedulerCleaned);
2812
2813 sleepless_testing::at(TestCheckPoint::AfterTrashedSchedulerCleaned);
2815 assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0);
2816 }
2817
2818 #[test]
2819 fn test_scheduler_schedule_execution_failure_with_extra_tx() {
2820 do_test_scheduler_schedule_execution_failure(true);
2821 }
2822
2823 #[test]
2824 fn test_scheduler_schedule_execution_failure_without_extra_tx() {
2825 do_test_scheduler_schedule_execution_failure(false);
2826 }
2827
2828 #[test]
2829 #[should_panic(expected = "This panic should be propagated. (From: ")]
2830 fn test_scheduler_schedule_execution_panic() {
2831 agave_logger::setup();
2832
2833 #[derive(Debug)]
2834 enum PanickingHanlderCheckPoint {
2835 BeforeNotifiedPanic,
2836 BeforeIgnoredPanic,
2837 }
2838
2839 let progress = sleepless_testing::setup(&[
2840 &TestCheckPoint::BeforeNewTask,
2841 &CheckPoint::NewTask(0),
2842 &PanickingHanlderCheckPoint::BeforeNotifiedPanic,
2843 &CheckPoint::SchedulerThreadAborted,
2844 &PanickingHanlderCheckPoint::BeforeIgnoredPanic,
2845 &TestCheckPoint::BeforeEndSession,
2846 ]);
2847
2848 #[derive(Debug)]
2849 struct PanickingHandler;
2850 impl TaskHandler for PanickingHandler {
2851 fn handle(
2852 _result: &mut Result<()>,
2853 _timings: &mut ExecuteTimings,
2854 _scheduling_context: &SchedulingContext,
2855 task: &Task,
2856 _handler_context: &HandlerContext,
2857 ) {
2858 let task_id = task.task_id();
2859 if task_id == 0 {
2860 sleepless_testing::at(PanickingHanlderCheckPoint::BeforeNotifiedPanic);
2861 } else if task_id == 1 {
2862 sleepless_testing::at(PanickingHanlderCheckPoint::BeforeIgnoredPanic);
2863 } else {
2864 unreachable!();
2865 }
2866 panic!("This panic should be propagated.");
2867 }
2868 }
2869
2870 let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
2871
2872 let bank = Bank::new_for_tests(&genesis_config);
2873 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2874
2875 const TX_COUNT: OrderedTaskId = 2;
2879
2880 let pool = SchedulerPool::<PooledScheduler<PanickingHandler>, _>::new_dyn_for_verification(
2881 Some(TX_COUNT.try_into().unwrap()), None,
2883 None,
2884 None,
2885 None,
2886 );
2887 let context = SchedulingContext::new(bank.clone());
2888
2889 let scheduler = pool.take_scheduler(context).unwrap();
2890
2891 for task_id in 0..TX_COUNT {
2892 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2894 &Keypair::new(),
2895 &solana_pubkey::new_rand(),
2896 1,
2897 genesis_config.hash(),
2898 ));
2899 scheduler.schedule_execution(tx, task_id).unwrap();
2900 }
2901 sleepless_testing::at(TestCheckPoint::BeforeNewTask);
2904
2905 sleepless_testing::at(TestCheckPoint::BeforeEndSession);
2906 let bank = BankWithScheduler::new(bank, Some(scheduler));
2907
2908 drop(progress);
2910 bank.wait_for_completed_scheduler().unwrap().0.unwrap();
2911 }
2912
2913 #[test]
2914 fn test_scheduler_execution_failure_short_circuiting() {
2915 agave_logger::setup();
2916
2917 let _progress = sleepless_testing::setup(&[
2918 &TestCheckPoint::BeforeNewTask,
2919 &CheckPoint::NewTask(0),
2920 &CheckPoint::TaskHandled(0),
2921 &CheckPoint::SchedulerThreadAborted,
2922 &TestCheckPoint::AfterSchedulerThreadAborted,
2923 ]);
2924
2925 static TASK_COUNT: Mutex<usize> = Mutex::new(0);
2926
2927 #[derive(Debug)]
2928 struct CountingFaultyHandler;
2929 impl TaskHandler for CountingFaultyHandler {
2930 fn handle(
2931 result: &mut Result<()>,
2932 _timings: &mut ExecuteTimings,
2933 _scheduling_context: &SchedulingContext,
2934 task: &Task,
2935 _handler_context: &HandlerContext,
2936 ) {
2937 let task_id = task.task_id();
2938 *TASK_COUNT.lock().unwrap() += 1;
2939 if task_id == 1 {
2940 *result = Err(TransactionError::AccountNotFound);
2941 }
2942 sleepless_testing::at(CheckPoint::TaskHandled(task_id));
2943 }
2944 }
2945
2946 let GenesisConfigInfo {
2947 genesis_config,
2948 mint_keypair,
2949 ..
2950 } = create_genesis_config(10_000);
2951
2952 let bank = Bank::new_for_tests(&genesis_config);
2953 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
2954 let pool = SchedulerPool::<PooledScheduler<CountingFaultyHandler>, _>::new_for_verification(
2955 None, None, None, None, None,
2956 );
2957 let context = SchedulingContext::new(bank.clone());
2958 let scheduler = pool.do_take_scheduler(context);
2959
2960 for i in 0..10 {
2961 let tx = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
2962 &mint_keypair,
2963 &solana_pubkey::new_rand(),
2964 2,
2965 genesis_config.hash(),
2966 ));
2967 scheduler.schedule_execution(tx, i).unwrap();
2968 }
2969 sleepless_testing::at(TestCheckPoint::BeforeNewTask);
2972
2973 let bank = BankWithScheduler::new(bank, Some(Box::new(scheduler)));
2975 assert_matches!(
2976 bank.wait_for_completed_scheduler(),
2977 Some((Err(TransactionError::AccountNotFound), _timings))
2978 );
2979 sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted);
2980 assert!(*TASK_COUNT.lock().unwrap() < 10);
2981 }
2982
2983 fn create_genesis_config_for_block_production(lamports: u64) -> GenesisConfigInfo {
2984 solana_ledger::genesis_utils::create_genesis_config(lamports)
2991 }
2992
2993 #[test]
2994 fn test_scheduler_schedule_execution_blocked_at_session_ending() {
2995 agave_logger::setup();
2996
2997 const STALLED_TRANSACTION_INDEX: OrderedTaskId = 0;
2998 const BLOCKED_TRANSACTION_INDEX: OrderedTaskId = 1;
2999
3000 let _progress = sleepless_testing::setup(&[
3001 &CheckPoint::BufferedOrDroppedTask(BLOCKED_TRANSACTION_INDEX),
3002 &TestCheckPoint::AfterBufferedTask,
3003 &CheckPoint::SessionEnding,
3004 &TestCheckPoint::AfterSessionEnding,
3005 ]);
3006
3007 #[derive(Debug)]
3008 struct StallingHandler;
3009 impl TaskHandler for StallingHandler {
3010 fn handle(
3011 result: &mut Result<()>,
3012 timings: &mut ExecuteTimings,
3013 scheduling_context: &SchedulingContext,
3014 task: &Task,
3015 handler_context: &HandlerContext,
3016 ) {
3017 let task_id = task.task_id();
3018 match task_id {
3019 STALLED_TRANSACTION_INDEX => {
3020 sleepless_testing::at(TestCheckPoint::AfterSessionEnding);
3021 }
3022 BLOCKED_TRANSACTION_INDEX => {}
3023 _ => unreachable!(),
3024 };
3025
3026 DefaultTaskHandler::handle(
3027 result,
3028 timings,
3029 scheduling_context,
3030 task,
3031 handler_context,
3032 );
3033 }
3034 }
3035
3036 let GenesisConfigInfo {
3037 genesis_config,
3038 mint_keypair,
3039 ..
3040 } = create_genesis_config_for_block_production(10_000);
3041
3042 let tx0 = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
3044 &mint_keypair,
3045 &solana_pubkey::new_rand(),
3046 2,
3047 genesis_config.hash(),
3048 ));
3049 let tx1 = RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
3050 &mint_keypair,
3051 &solana_pubkey::new_rand(),
3052 2,
3053 genesis_config.hash(),
3054 ));
3055
3056 let bank = Bank::new_for_tests(&genesis_config);
3057 let (bank, _bank_forks) = setup_dummy_fork_graph(bank);
3058 let pool =
3059 SchedulerPool::<PooledScheduler<StallingHandler>, _>::new(None, None, None, None, None);
3060
3061 let mut expected_transaction_count = Saturating(0);
3064 assert_eq!(bank.transaction_count(), expected_transaction_count.0);
3065
3066 let context = SchedulingContext::new(bank.clone());
3067 let scheduler = pool.take_scheduler(context).unwrap();
3068 let old_scheduler_id = scheduler.id();
3069
3070 scheduler
3071 .schedule_execution(tx0, STALLED_TRANSACTION_INDEX)
3072 .unwrap();
3073 scheduler
3074 .schedule_execution(tx1, BLOCKED_TRANSACTION_INDEX)
3075 .unwrap();
3076
3077 let bank = BankWithScheduler::new(bank, Some(scheduler));
3078
3079 sleepless_testing::at(TestCheckPoint::AfterBufferedTask);
3080
3081 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
3082
3083 expected_transaction_count += 1;
3084 expected_transaction_count += 1;
3087 assert_eq!(bank.transaction_count(), expected_transaction_count.0);
3088
3089 let bank = Arc::new(Bank::new_from_parent(
3091 bank.clone_without_scheduler(),
3092 SlotLeader::default(),
3093 bank.slot().checked_add(1).unwrap(),
3094 ));
3095 assert_eq!(bank.transaction_count(), expected_transaction_count.0);
3096
3097 let context = SchedulingContext::new(bank.clone());
3098 let scheduler = pool.take_scheduler(context).unwrap();
3099 assert_eq!(scheduler.id(), old_scheduler_id);
3102
3103 let bank = BankWithScheduler::new(bank, Some(scheduler));
3104 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
3105
3106 assert_eq!(bank.transaction_count(), expected_transaction_count.0);
3107 }
3108
3109 #[test]
3110 fn test_scheduler_mismatched_scheduling_context_race() {
3111 agave_logger::setup();
3112
3113 #[derive(Debug)]
3114 struct TaskAndContextChecker;
3115 impl TaskHandler for TaskAndContextChecker {
3116 fn handle(
3117 _result: &mut Result<()>,
3118 _timings: &mut ExecuteTimings,
3119 scheduling_context: &SchedulingContext,
3120 task: &Task,
3121 _handler_context: &HandlerContext,
3122 ) {
3123 assert_eq!(task.task_id() as Slot, scheduling_context.slot());
3125 }
3126 }
3127
3128 let GenesisConfigInfo {
3129 genesis_config,
3130 mint_keypair,
3131 ..
3132 } = create_genesis_config(10_000);
3133
3134 let bank0 = Bank::new_for_tests(&genesis_config);
3136 let (bank0, _bank_forks) = setup_dummy_fork_graph(bank0);
3137 let bank1 = Arc::new(Bank::new_from_parent(
3138 bank0.clone(),
3139 SlotLeader::default(),
3140 bank0.slot().checked_add(1).unwrap(),
3141 ));
3142
3143 let pool = SchedulerPool::<PooledScheduler<TaskAndContextChecker>, _>::new_for_verification(
3144 Some(4), None,
3146 None,
3147 None,
3148 None,
3149 );
3150
3151 let dummy_tx =
3153 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
3154 &mint_keypair,
3155 &solana_pubkey::new_rand(),
3156 2,
3157 genesis_config.hash(),
3158 ));
3159 let context0 = &SchedulingContext::new(bank0.clone());
3160 let context1 = &SchedulingContext::new(bank1.clone());
3161
3162 for (context, task_id) in [(context0, 0), (context1, 1)]
3164 .into_iter()
3165 .cycle()
3166 .take(10000)
3167 {
3168 let scheduler = pool.take_scheduler(context.clone()).unwrap();
3169 scheduler
3170 .schedule_execution(dummy_tx.clone(), task_id)
3171 .unwrap();
3172 scheduler.wait_for_termination(false).1.return_to_pool();
3173 }
3174 }
3175
3176 #[derive(Debug)]
3177 struct AsyncScheduler<const TRIGGER_RACE_CONDITION: bool>(
3178 Mutex<ResultWithTimings>,
3179 Mutex<Vec<JoinHandle<ResultWithTimings>>>,
3180 SchedulingContext,
3181 Arc<SchedulerPool<Self, DefaultTaskHandler>>,
3182 );
3183
3184 impl<const TRIGGER_RACE_CONDITION: bool> AsyncScheduler<TRIGGER_RACE_CONDITION> {
3185 fn do_wait(&self) {
3186 let mut overall_result = Ok(());
3187 let mut overall_timings = ExecuteTimings::default();
3188 for handle in self.1.lock().unwrap().drain(..) {
3189 let (result, timings) = handle.join().unwrap();
3190 match result {
3191 Ok(()) => {}
3192 Err(e) => overall_result = Err(e),
3193 }
3194 overall_timings.accumulate(&timings);
3195 }
3196 *self.0.lock().unwrap() = (overall_result, overall_timings);
3197 }
3198 }
3199
3200 impl<const TRIGGER_RACE_CONDITION: bool> InstalledScheduler
3201 for AsyncScheduler<TRIGGER_RACE_CONDITION>
3202 {
3203 fn id(&self) -> SchedulerId {
3204 unimplemented!();
3205 }
3206
3207 fn context(&self) -> &SchedulingContext {
3208 &self.2
3209 }
3210
3211 fn schedule_execution(
3212 &self,
3213 transaction: RuntimeTransaction<SanitizedTransaction>,
3214 task_id: OrderedTaskId,
3215 ) -> ScheduleResult {
3216 let context = self.context().clone();
3217 let pool = self.3.clone();
3218
3219 self.1.lock().unwrap().push(std::thread::spawn(move || {
3220 std::thread::sleep(std::time::Duration::from_secs(1));
3223
3224 let mut result = Ok(());
3225 let mut timings = ExecuteTimings::default();
3226
3227 let task = SchedulingStateMachine::create_task(transaction, task_id, &mut |_| {
3228 UsageQueue::new(&Capability::FifoQueueing)
3229 });
3230
3231 <DefaultTaskHandler as TaskHandler>::handle(
3232 &mut result,
3233 &mut timings,
3234 &context,
3235 &task,
3236 &pool.create_handler_context(),
3237 );
3238 (result, timings)
3239 }));
3240
3241 Ok(())
3242 }
3243
3244 fn unpause_after_taken(&self) {
3245 unimplemented!();
3246 }
3247
3248 fn recover_error_after_abort(&mut self) -> TransactionError {
3249 unimplemented!();
3250 }
3251
3252 fn wait_for_termination(
3253 self: Box<Self>,
3254 _is_dropped: bool,
3255 ) -> (ResultWithTimings, UninstalledSchedulerBox) {
3256 self.do_wait();
3257 let result_with_timings = std::mem::replace(
3258 &mut *self.0.lock().unwrap(),
3259 initialized_result_with_timings(),
3260 );
3261 (result_with_timings, self)
3262 }
3263
3264 fn pause_for_recent_blockhash(&mut self) {
3265 if TRIGGER_RACE_CONDITION {
3266 return;
3269 }
3270 self.do_wait();
3271 }
3272 }
3273
3274 impl<const TRIGGER_RACE_CONDITION: bool> SchedulerInner for AsyncScheduler<TRIGGER_RACE_CONDITION> {
3275 fn id(&self) -> SchedulerId {
3276 42
3277 }
3278
3279 fn is_trashed(&self) -> bool {
3280 false
3281 }
3282
3283 fn is_overgrown(&self) -> bool {
3284 unimplemented!()
3285 }
3286
3287 fn discard_buffer(&self) {
3288 unimplemented!()
3289 }
3290 }
3291
3292 impl<const TRIGGER_RACE_CONDITION: bool> UninstalledScheduler
3293 for AsyncScheduler<TRIGGER_RACE_CONDITION>
3294 {
3295 fn return_to_pool(self: Box<Self>) {
3296 self.3.clone().return_scheduler(*self)
3297 }
3298 }
3299
3300 impl<const TRIGGER_RACE_CONDITION: bool> SpawnableScheduler<DefaultTaskHandler>
3301 for AsyncScheduler<TRIGGER_RACE_CONDITION>
3302 {
3303 type Inner = Self;
3305
3306 fn into_inner(self) -> (ResultWithTimings, Self::Inner) {
3307 unimplemented!();
3308 }
3309
3310 fn from_inner(
3311 _inner: Self::Inner,
3312 _context: SchedulingContext,
3313 _result_with_timings: ResultWithTimings,
3314 ) -> Self {
3315 unimplemented!();
3316 }
3317
3318 fn spawn(
3319 pool: Arc<SchedulerPool<Self, DefaultTaskHandler>>,
3320 context: SchedulingContext,
3321 _result_with_timings: ResultWithTimings,
3322 ) -> Self {
3323 AsyncScheduler::<TRIGGER_RACE_CONDITION>(
3324 Mutex::new(initialized_result_with_timings()),
3325 Mutex::new(vec![]),
3326 context,
3327 pool,
3328 )
3329 }
3330 }
3331
3332 fn do_test_scheduler_schedule_execution_recent_blockhash_edge_case<
3333 const TRIGGER_RACE_CONDITION: bool,
3334 >() {
3335 agave_logger::setup();
3336
3337 let GenesisConfigInfo {
3338 genesis_config,
3339 mint_keypair,
3340 ..
3341 } = create_genesis_config(10_000);
3342 let very_old_valid_tx =
3343 RuntimeTransaction::from_transaction_for_tests(system_transaction::transfer(
3344 &mint_keypair,
3345 &solana_pubkey::new_rand(),
3346 2,
3347 genesis_config.hash(),
3348 ));
3349 let bank = Bank::new_for_tests(&genesis_config);
3350 let (mut bank, _bank_forks) = setup_dummy_fork_graph(bank);
3351 for _ in 0..bank.max_processing_age() {
3352 bank.fill_bank_with_ticks_for_tests();
3353 bank.freeze();
3354 let slot = bank.slot();
3355 bank = Arc::new(Bank::new_from_parent(
3356 bank,
3357 SlotLeader::default(),
3358 slot.checked_add(1).unwrap(),
3359 ));
3360 }
3361 let context = SchedulingContext::new(bank.clone());
3362
3363 let pool =
3364 SchedulerPool::<AsyncScheduler<TRIGGER_RACE_CONDITION>, DefaultTaskHandler>::new_dyn_for_verification(
3365 None,
3366 None,
3367 None,
3368 None,
3369 None,
3370 );
3371 let scheduler = pool.take_scheduler(context).unwrap();
3372
3373 let bank = BankWithScheduler::new(bank, Some(scheduler));
3374 assert_eq!(bank.transaction_count(), 0);
3375
3376 bank.schedule_transaction_executions([(very_old_valid_tx, 0)].into_iter())
3378 .unwrap();
3379 bank.fill_bank_with_ticks_for_tests();
3381
3382 if TRIGGER_RACE_CONDITION {
3383 assert_matches!(
3385 bank.wait_for_completed_scheduler(),
3386 Some((Err(TransactionError::BlockhashNotFound), _))
3387 );
3388 assert_eq!(bank.transaction_count(), 0);
3389 } else {
3390 assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _)));
3391 assert_eq!(bank.transaction_count(), 1);
3392 }
3393 }
3394
3395 #[test]
3396 fn test_scheduler_schedule_execution_recent_blockhash_edge_case_with_race() {
3397 do_test_scheduler_schedule_execution_recent_blockhash_edge_case::<true>();
3398 }
3399
3400 #[test]
3401 fn test_scheduler_schedule_execution_recent_blockhash_edge_case_without_race() {
3402 do_test_scheduler_schedule_execution_recent_blockhash_edge_case::<false>();
3403 }
3404
3405 #[test]
3406 fn test_default_handler_count() {
3407 for (detected, expected) in [(32, 8), (4, 1), (2, 1)] {
3408 assert_eq!(
3409 DefaultSchedulerPool::calculate_default_handler_count(Some(detected)),
3410 expected
3411 );
3412 }
3413 assert_eq!(
3414 DefaultSchedulerPool::calculate_default_handler_count(None),
3415 4
3416 );
3417 }
3418
3419 #[test]
3421 fn test_enfoced_get_account_locks_validation() {
3422 agave_logger::setup();
3423
3424 let GenesisConfigInfo {
3425 genesis_config,
3426 ref mint_keypair,
3427 ..
3428 } = create_genesis_config(10_000);
3429 let bank = Bank::new_for_tests(&genesis_config);
3430 let (bank, _bank_forks) = &setup_dummy_fork_graph(bank);
3431
3432 let mut tx = system_transaction::transfer(
3433 mint_keypair,
3434 &solana_pubkey::new_rand(),
3435 2,
3436 genesis_config.hash(),
3437 );
3438 tx.message.account_keys.push(tx.message.account_keys[0]);
3440 let tx = RuntimeTransaction::from_transaction_for_tests(tx);
3441
3442 let result = &mut Ok(());
3444 let timings = &mut ExecuteTimings::default();
3445 let scheduling_context = &SchedulingContext::new(bank.clone());
3446 let handler_context = &HandlerContext {
3447 thread_count: 0,
3448 log_messages_bytes_limit: None,
3449 transaction_status_sender: None,
3450 replay_vote_sender: None,
3451 prioritization_fee_cache: None,
3452 };
3453
3454 let task = SchedulingStateMachine::create_task(tx, 0, &mut |_| {
3455 UsageQueue::new(&Capability::FifoQueueing)
3456 });
3457 DefaultTaskHandler::handle(result, timings, scheduling_context, &task, handler_context);
3458 assert_matches!(result, Err(TransactionError::AccountLoadedTwice));
3459 }
3460}