Skip to main content

solana_runtime/
accounts_background_service.rs

1//! Service to clean up dead slots in accounts_db
2//!
3//! This can be expensive since we have to walk the append vecs being cleaned up.
4
5mod pending_snapshot_packages;
6mod stats;
7pub use pending_snapshot_packages::PendingSnapshotPackages;
8#[cfg(feature = "dev-context-only-utils")]
9use qualifier_attr::qualifiers;
10use {
11    crate::{
12        bank::{Bank, BankSlotDelta, DropCallback},
13        bank_forks::BankForks,
14        snapshot_controller::SnapshotController,
15        snapshot_package::SnapshotPackage,
16    },
17    agave_snapshots::{SnapshotArchiveKind, SnapshotKind, error::SnapshotError},
18    crossbeam_channel::{Receiver, SendError, Sender},
19    log::*,
20    rayon::iter::{IntoParallelIterator, ParallelIterator},
21    solana_clock::{BankId, Slot},
22    solana_measure::{measure::Measure, measure_us},
23    stats::StatsManager,
24    std::{
25        boxed::Box,
26        cmp,
27        fmt::{self, Debug, Formatter},
28        sync::{
29            Arc, LazyLock, Mutex, RwLock,
30            atomic::{AtomicBool, AtomicU64, Ordering},
31        },
32        thread::{self, Builder, JoinHandle, sleep},
33        time::{Duration, Instant},
34    },
35};
36
37/// Limit the maximum frequency that the ABS main loop can run.
38/// If the loop ran for less than this duration, sleep the remainder.
39/// E.g. with a min interval of 100 millis, the loop will run a maximum
40/// of 10 times per second.  Lower frequency is allowed, and occurs
41/// when longer-running tasks are triggered.
42const MIN_LOOP_INTERVAL: Duration = Duration::from_millis(100);
43// Set the clean interval duration to be approximately how long before the next incremental
44// snapshot request is received, plus some buffer.  The default incremental snapshot interval is
45// 100 slots, which ends up being 40 seconds plus buffer.
46const CLEAN_INTERVAL: Duration = Duration::from_secs(50);
47const SHRINK_INTERVAL: Duration = Duration::from_secs(1);
48
49pub type SnapshotRequestSender = Sender<SnapshotRequest>;
50pub type SnapshotRequestReceiver = Receiver<SnapshotRequest>;
51pub type DroppedSlotsSender = Sender<(Slot, BankId)>;
52pub type DroppedSlotsReceiver = Receiver<(Slot, BankId)>;
53
54/// interval to report bank_drop queue events: 60s
55const BANK_DROP_SIGNAL_CHANNEL_REPORT_INTERVAL: u64 = 60_000;
56/// maximum drop bank signal queue length
57const MAX_DROP_BANK_SIGNAL_QUEUE_SIZE: usize = 10_000;
58
59#[derive(Debug, Default)]
60struct PrunedBankQueueLenReporter {
61    last_report_time: AtomicU64,
62}
63
64impl PrunedBankQueueLenReporter {
65    fn report(&self, q_len: usize) {
66        let now = solana_time_utils::timestamp();
67        let last_report_time = self.last_report_time.load(Ordering::Acquire);
68        if q_len > MAX_DROP_BANK_SIGNAL_QUEUE_SIZE
69            && now.saturating_sub(last_report_time) > BANK_DROP_SIGNAL_CHANNEL_REPORT_INTERVAL
70        {
71            datapoint_warn!("excessive_pruned_bank_channel_len", ("len", q_len, i64));
72            self.last_report_time.store(now, Ordering::Release);
73        }
74    }
75}
76
77static BANK_DROP_QUEUE_REPORTER: LazyLock<PrunedBankQueueLenReporter> =
78    LazyLock::new(PrunedBankQueueLenReporter::default);
79
80#[derive(Clone)]
81pub struct SendDroppedBankCallback {
82    sender: DroppedSlotsSender,
83}
84
85impl DropCallback for SendDroppedBankCallback {
86    fn callback(&self, bank: &Bank) {
87        BANK_DROP_QUEUE_REPORTER.report(self.sender.len());
88        if let Err(SendError(_)) = self.sender.send((bank.slot(), bank.bank_id())) {
89            info!("bank DropCallback signal queue disconnected.");
90        }
91    }
92
93    fn clone_box(&self) -> Box<dyn DropCallback + Send + Sync> {
94        Box::new(self.clone())
95    }
96}
97
98impl Debug for SendDroppedBankCallback {
99    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
100        write!(f, "SendDroppedBankCallback({self:p})")
101    }
102}
103
104impl SendDroppedBankCallback {
105    pub fn new(sender: DroppedSlotsSender) -> Self {
106        Self { sender }
107    }
108}
109
110pub struct SnapshotRequest {
111    pub snapshot_root_bank: Arc<Bank>,
112    pub status_cache_slot_deltas: Vec<BankSlotDelta>,
113    pub request_kind: SnapshotRequestKind,
114
115    /// The instant this request was send to the queue.
116    /// Used to track how long requests wait before processing.
117    pub enqueued: Instant,
118}
119
120impl Debug for SnapshotRequest {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.debug_struct("SnapshotRequest")
123            .field("request kind", &self.request_kind)
124            .field("bank slot", &self.snapshot_root_bank.slot())
125            .field("block height", &self.snapshot_root_bank.block_height())
126            .finish_non_exhaustive()
127    }
128}
129
130/// What kind of request is this?
131#[derive(Debug, Copy, Clone, Eq, PartialEq)]
132pub enum SnapshotRequestKind {
133    FullSnapshot,
134    IncrementalSnapshot,
135    FastbootSnapshot,
136}
137
138pub struct SnapshotRequestHandler {
139    pub snapshot_controller: Arc<SnapshotController>,
140    pub snapshot_request_receiver: SnapshotRequestReceiver,
141    pub pending_snapshot_packages: Arc<Mutex<PendingSnapshotPackages>>,
142}
143
144impl SnapshotRequestHandler {
145    // Returns the latest requested snapshot slot and storages
146    pub fn handle_snapshot_requests(
147        &self,
148        non_snapshot_time_us: u128,
149    ) -> Option<Result<Slot, SnapshotError>> {
150        let (snapshot_request, num_outstanding_requests, num_re_enqueued_requests) =
151            self.get_next_snapshot_request()?;
152
153        datapoint_info!(
154            "handle_snapshot_requests",
155            ("num_outstanding_requests", num_outstanding_requests, i64),
156            ("num_re_enqueued_requests", num_re_enqueued_requests, i64),
157            (
158                "enqueued_time_us",
159                snapshot_request.enqueued.elapsed().as_micros(),
160                i64
161            ),
162        );
163
164        let snapshot_kind = new_snapshot_kind(&snapshot_request)?;
165        Some(self.handle_snapshot_request(non_snapshot_time_us, snapshot_request, snapshot_kind))
166    }
167
168    /// Get the next snapshot request to handle
169    ///
170    /// Look through the snapshot request channel to find the highest priority one to handle next.
171    /// If there are no snapshot requests in the channel, return None.  Otherwise return the
172    /// highest priority one.  Unhandled snapshot requests with slots GREATER-THAN the handled one
173    /// will be re-enqueued.  The remaining will be dropped.
174    ///
175    /// Also return the number of snapshot requests initially in the channel, and the number of
176    /// ones re-enqueued.
177    fn get_next_snapshot_request(
178        &self,
179    ) -> Option<(
180        SnapshotRequest,
181        /*num outstanding snapshot requests*/ usize,
182        /*num re-enqueued snapshot requests*/ usize,
183    )> {
184        let mut requests: Vec<_> = self.snapshot_request_receiver.try_iter().collect();
185        let requests_len = requests.len();
186        debug!("outstanding snapshot requests ({requests_len}): {requests:?}");
187
188        match requests_len {
189            0 => None,
190            1 => {
191                // SAFETY: We know the len is 1, so `pop` will return `Some`
192                let snapshot_request = requests.pop().unwrap();
193                Some((snapshot_request, 1, 0))
194            }
195            _ => {
196                let max_idx = requests
197                    .iter()
198                    .enumerate()
199                    .max_by(|(_, a), (_, b)| cmp_requests_by_priority(a, b))
200                    .map(|(idx, _)| idx)
201                    .unwrap(); // SAFETY: We know len > 1
202                let snapshot_request = requests.swap_remove(max_idx);
203                let handled_request_slot = snapshot_request.snapshot_root_bank.slot();
204                // re-enqueue any remaining requests for slots GREATER-THAN the one that will be handled
205                let num_re_enqueued_requests = requests
206                    .into_iter()
207                    .filter(|snapshot_request| {
208                        snapshot_request.snapshot_root_bank.slot() > handled_request_slot
209                    })
210                    .map(|snapshot_request| {
211                        self.snapshot_controller
212                            .request_sender()
213                            .try_send(snapshot_request)
214                            .expect("re-enqueue snapshot request");
215                    })
216                    .count();
217
218                Some((snapshot_request, requests_len, num_re_enqueued_requests))
219            }
220        }
221    }
222
223    fn handle_snapshot_request(
224        &self,
225        non_snapshot_time_us: u128,
226        snapshot_request: SnapshotRequest,
227        snapshot_kind: SnapshotKind,
228    ) -> Result<Slot, SnapshotError> {
229        info!("handling snapshot request: {snapshot_request:?}, {snapshot_kind:?}");
230        let mut total_time = Measure::start("snapshot_request_receiver_total_time");
231        let SnapshotRequest {
232            snapshot_root_bank,
233            status_cache_slot_deltas,
234            request_kind: _,
235            enqueued: _,
236        } = snapshot_request;
237
238        if snapshot_kind.is_full_snapshot() {
239            // The latest full snapshot slot is what accounts-db uses to properly handle
240            // zero lamport accounts.  We are handling a full snapshot request here, and
241            // since taking a snapshot is not allowed to fail, we can update accounts-db now.
242            snapshot_root_bank
243                .rc
244                .accounts
245                .accounts_db
246                .set_latest_full_snapshot_slot(snapshot_root_bank.slot());
247        }
248
249        let mut flush_accounts_cache_time = Measure::start("flush_accounts_cache_time");
250        // Forced cache flushing MUST flush all roots <= snapshot_root_bank.slot().
251        // That's because `snapshot_root_bank.slot()` must be root at this point,
252        // and contains relevant updates because each bank has at least 1 account update due
253        // to sysvar maintenance. Otherwise, this would cause missing storages in the snapshot
254        snapshot_root_bank.force_flush_accounts_cache();
255        // Ensure all roots <= `self.slot()` have been flushed.
256        // Note `max_flush_root` could be larger than self.slot() if there are
257        // `> MAX_CACHE_SLOT` cached and rooted slots which triggered earlier flushes.
258        assert!(
259            snapshot_root_bank.slot()
260                <= snapshot_root_bank
261                    .rc
262                    .accounts
263                    .accounts_db
264                    .accounts_cache
265                    .fetch_max_flush_root()
266                    .expect("Roots have been flushed")
267        );
268        flush_accounts_cache_time.stop();
269
270        let mut clean_time = Measure::start("clean_time");
271        snapshot_root_bank.clean_accounts();
272        clean_time.stop();
273
274        let (_, shrink_ancient_time_us) = measure_us!(snapshot_root_bank.shrink_ancient_slots());
275
276        let mut shrink_time = Measure::start("shrink_time");
277        snapshot_root_bank.shrink_candidate_slots();
278        shrink_time.stop();
279
280        // Snapshot the bank and send over a snapshot package
281        let mut snapshot_time = Measure::start("snapshot_time");
282        let snapshot_package = SnapshotPackage::new(
283            snapshot_kind,
284            &snapshot_root_bank,
285            snapshot_root_bank.get_snapshot_storages(None),
286            status_cache_slot_deltas,
287        );
288        self.pending_snapshot_packages
289            .lock()
290            .unwrap()
291            .push(snapshot_package);
292        snapshot_time.stop();
293        info!(
294            "Handled snapshot request. snapshot kind: {:?}, slot: {}, bank hash: {}",
295            snapshot_kind,
296            snapshot_root_bank.slot(),
297            snapshot_root_bank.hash(),
298        );
299
300        total_time.stop();
301
302        datapoint_info!(
303            "handle_snapshot_requests-timing",
304            (
305                "flush_accounts_cache_time",
306                flush_accounts_cache_time.as_us(),
307                i64
308            ),
309            ("shrink_time", shrink_time.as_us(), i64),
310            ("clean_time", clean_time.as_us(), i64),
311            ("snapshot_time", snapshot_time.as_us(), i64),
312            ("total_us", total_time.as_us(), i64),
313            ("non_snapshot_time_us", non_snapshot_time_us, i64),
314            ("shrink_ancient_time_us", shrink_ancient_time_us, i64),
315        );
316        Ok(snapshot_root_bank.slot())
317    }
318
319    /// Returns the slot of the next snapshot request to be handled
320    fn peek_next_snapshot_request_slot(&self) -> Option<Slot> {
321        // We reuse `get_next_snapshot_request()` here, since it already implements all the logic
322        // for getting the highest priority request, *AND* we leverage its test coverage.
323        // Additionally, since `get_next_snapshot_request()` drops old requests, we might get to
324        // proactively clean up old banks earlier as well!
325        let (next_request, _, _) = self.get_next_snapshot_request()?;
326        let next_slot = next_request.snapshot_root_bank.slot();
327
328        // make sure to re-enqueue the request, otherwise we'd lose it!
329        self.snapshot_controller
330            .request_sender()
331            .try_send(next_request)
332            .expect("re-enqueue snapshot request");
333
334        Some(next_slot)
335    }
336}
337
338#[derive(Debug)]
339pub struct PrunedBanksRequestHandler {
340    pub pruned_banks_receiver: DroppedSlotsReceiver,
341}
342
343impl PrunedBanksRequestHandler {
344    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
345    fn handle_request(&self, bank: &Bank) -> usize {
346        let mut banks_to_purge: Vec<_> = self.pruned_banks_receiver.try_iter().collect();
347        // We need a stable sort to ensure we purge banks—with the same slot—in the same order
348        // they were sent into the channel.
349        banks_to_purge.sort_by_key(|(slot, _id)| *slot);
350        let num_banks_to_purge = banks_to_purge.len();
351
352        // Group the banks into slices with the same slot
353        let grouped_banks_to_purge: Vec<_> = banks_to_purge.chunk_by(|a, b| a.0 == b.0).collect();
354
355        // Log whenever we need to handle banks with the same slot.  Purposely do this *before* we
356        // call `purge_slot()` to ensure we get the datapoint (in case there's an assert/panic).
357        let num_banks_with_same_slot =
358            num_banks_to_purge.saturating_sub(grouped_banks_to_purge.len());
359        if num_banks_with_same_slot > 0 {
360            datapoint_info!(
361                "pruned_banks_request_handler",
362                ("num_pruned_banks", num_banks_to_purge, i64),
363                ("num_banks_with_same_slot", num_banks_with_same_slot, i64),
364            );
365        }
366
367        // Purge all the slots in parallel
368        // Banks for the same slot are purged sequentially
369        let accounts_db = bank.rc.accounts.accounts_db.as_ref();
370        accounts_db.thread_pool_background.install(|| {
371            grouped_banks_to_purge.into_par_iter().for_each(|group| {
372                group.iter().for_each(|(slot, bank_id)| {
373                    accounts_db.purge_slot(*slot, *bank_id, true);
374                })
375            });
376        });
377
378        num_banks_to_purge
379    }
380
381    fn remove_dead_slots(
382        &self,
383        bank: &Bank,
384        removed_slots_count: &mut usize,
385        total_remove_slots_time: &mut u64,
386    ) {
387        let mut remove_slots_time = Measure::start("remove_slots_time");
388        *removed_slots_count += self.handle_request(bank);
389        remove_slots_time.stop();
390        *total_remove_slots_time += remove_slots_time.as_us();
391
392        if *removed_slots_count >= 100 {
393            datapoint_info!(
394                "remove_slots_timing",
395                ("remove_slots_time", *total_remove_slots_time, i64),
396                ("removed_slots_count", *removed_slots_count, i64),
397            );
398            *total_remove_slots_time = 0;
399            *removed_slots_count = 0;
400        }
401    }
402}
403
404pub struct AbsRequestHandlers {
405    pub snapshot_request_handler: SnapshotRequestHandler,
406    pub pruned_banks_request_handler: PrunedBanksRequestHandler,
407}
408
409impl AbsRequestHandlers {
410    // Returns the latest requested snapshot slot, if one exists
411    pub fn handle_snapshot_requests(
412        &self,
413        non_snapshot_time_us: u128,
414    ) -> Option<Result<Slot, SnapshotError>> {
415        self.snapshot_request_handler
416            .handle_snapshot_requests(non_snapshot_time_us)
417    }
418}
419
420pub struct AccountsBackgroundService {
421    t_background: JoinHandle<()>,
422    status: AbsStatus,
423}
424
425impl AccountsBackgroundService {
426    pub fn new(
427        bank_forks: Arc<RwLock<BankForks>>,
428        exit: Arc<AtomicBool>,
429        request_handlers: AbsRequestHandlers,
430    ) -> Self {
431        let is_running = Arc::new(AtomicBool::new(true));
432        let stop = Arc::new(AtomicBool::new(false));
433        let mut last_cleaned_slot = 0;
434        let mut removed_slots_count = 0;
435        let mut total_remove_slots_time = 0;
436        let t_background = Builder::new()
437            .name("solAcctsBgSvc".to_string())
438            .spawn({
439                let is_running = is_running.clone();
440                let stop = stop.clone();
441
442                move || {
443                    info!("AccountsBackgroundService has started");
444                    let mut stats = StatsManager::new();
445                    let mut last_snapshot_end_time = None;
446                    let mut previous_clean_time = Instant::now();
447                    let mut previous_shrink_time = Instant::now();
448
449                    loop {
450                        if exit.load(Ordering::Relaxed) || stop.load(Ordering::Relaxed) {
451                            break;
452                        }
453                        let start_time = Instant::now();
454
455                        // Grab the current root bank
456                        let bank = bank_forks.read().unwrap().root_bank();
457
458                        // Purge accounts of any dead slots
459                        request_handlers
460                            .pruned_banks_request_handler
461                            .remove_dead_slots(
462                                &bank,
463                                &mut removed_slots_count,
464                                &mut total_remove_slots_time,
465                            );
466
467                        let non_snapshot_time = last_snapshot_end_time
468                            .map(|last_snapshot_end_time: Instant| {
469                                last_snapshot_end_time.elapsed().as_micros()
470                            })
471                            .unwrap_or_default();
472
473                        // Check to see if there were any requests for snapshotting banks
474                        // < the current root bank `bank` above.
475                        //
476                        // Claim: Any snapshot request for slot `N` found here implies that the
477                        // last cleanup slot `M` satisfies `M < N`
478                        //
479                        // Proof: Assume for contradiction that we find a snapshot request for slot
480                        // `N` here, but cleanup has already happened on some slot `M >= N`.
481                        // Because the call to `bank.clean_accounts(true)` (in the code below)
482                        // implies we only clean slots `<= bank - 1`, then that means in some
483                        // *previous* iteration of this loop, we must have gotten a root bank for
484                        // slot some slot `R` where `R > N`, but did not see the snapshot for `N`
485                        // in the snapshot request channel.
486                        //
487                        // However, this is impossible because BankForks.set_root() will always
488                        // flush the snapshot request for `N` to the snapshot request channel
489                        // before setting a root `R > N`, and
490                        // snapshot_request_handler.handle_requests() will always look for the
491                        // latest available snapshot in the channel.
492                        let snapshot_handle_result =
493                            request_handlers.handle_snapshot_requests(non_snapshot_time);
494
495                        if let Some(snapshot_handle_result) = snapshot_handle_result {
496                            // Safe, see proof above
497
498                            last_snapshot_end_time = Some(Instant::now());
499                            match snapshot_handle_result {
500                                Ok(snapshot_slot) => {
501                                    assert!(
502                                        last_cleaned_slot <= snapshot_slot,
503                                        "last cleaned slot: {last_cleaned_slot}, snapshot request \
504                                         slot: {snapshot_slot}, enqueued snapshot requests: {:?}",
505                                        request_handlers
506                                            .snapshot_request_handler
507                                            .snapshot_request_receiver
508                                            .try_iter()
509                                            .collect::<Vec<_>>(),
510                                    );
511                                    last_cleaned_slot = snapshot_slot;
512                                    previous_clean_time = Instant::now();
513                                    previous_shrink_time = Instant::now();
514                                }
515                                Err(err) => {
516                                    error!(
517                                        "Stopping AccountsBackgroundService! Fatal error while \
518                                         handling snapshot requests: {err}",
519                                    );
520                                    exit.store(true, Ordering::Relaxed);
521                                    break;
522                                }
523                            }
524                        } else {
525                            // we didn't handle a snapshot request, so do flush/clean/shrink
526
527                            let next_snapshot_request_slot = request_handlers
528                                .snapshot_request_handler
529                                .peek_next_snapshot_request_slot();
530
531                            // We cannot clean past the next snapshot request slot because it may
532                            // have zero-lamport accounts.  See the comments in
533                            // Bank::clean_accounts() for more information.
534                            let max_clean_slot_inclusive = cmp::min(
535                                next_snapshot_request_slot.unwrap_or(Slot::MAX),
536                                bank.slot(),
537                            )
538                            .saturating_sub(1);
539
540                            let duration_since_previous_clean = previous_clean_time.elapsed();
541                            let should_clean = duration_since_previous_clean > CLEAN_INTERVAL;
542
543                            // if we're cleaning, then force flush, otherwise be lazy
544                            let force_flush = should_clean;
545                            bank.rc
546                                .accounts
547                                .accounts_db
548                                .flush_accounts_cache(force_flush, Some(max_clean_slot_inclusive));
549
550                            if should_clean {
551                                bank.rc
552                                    .accounts
553                                    .accounts_db
554                                    .clean_accounts(Some(max_clean_slot_inclusive), false);
555                                last_cleaned_slot = max_clean_slot_inclusive;
556                                previous_clean_time = Instant::now();
557                            }
558
559                            let duration_since_previous_shrink = previous_shrink_time.elapsed();
560                            let should_shrink = duration_since_previous_shrink > SHRINK_INTERVAL;
561                            // To avoid pathological interactions between the clean and shrink
562                            // timers, call shrink for either should_shrink or should_clean.
563                            if should_shrink || should_clean {
564                                if should_clean {
565                                    // We used to only squash (aka shrink ancients) when we also
566                                    // cleaned, so keep that same behavior here for now.
567                                    bank.shrink_ancient_slots();
568                                }
569                                bank.shrink_candidate_slots();
570                                previous_shrink_time = Instant::now();
571                            }
572                        }
573
574                        let loop_dur = start_time.elapsed();
575                        stats.record_and_maybe_submit(loop_dur);
576                        if let Some(sleep_dur) = MIN_LOOP_INTERVAL.checked_sub(loop_dur) {
577                            sleep(sleep_dur);
578                        }
579                    }
580                    info!("AccountsBackgroundService has stopped");
581                    is_running.store(false, Ordering::Relaxed);
582                }
583            })
584            .unwrap();
585
586        Self {
587            t_background,
588            status: AbsStatus { is_running, stop },
589        }
590    }
591
592    /// Should be called immediately after bank_fork_utils::load_bank_forks(), and as such, there
593    /// should only be one bank, the root bank, in `bank_forks`
594    /// All banks added to `bank_forks` will be descended from the root bank, and thus will inherit
595    /// the bank drop callback.
596    pub fn setup_bank_drop_callback(bank_forks: Arc<RwLock<BankForks>>) -> DroppedSlotsReceiver {
597        assert_eq!(bank_forks.read().unwrap().banks().len(), 1);
598
599        let (pruned_banks_sender, pruned_banks_receiver) = crossbeam_channel::unbounded();
600        {
601            let root_bank = bank_forks.read().unwrap().root_bank();
602
603            root_bank
604                .rc
605                .accounts
606                .accounts_db
607                .enable_bank_drop_callback();
608            root_bank.set_callback(Some(Box::new(SendDroppedBankCallback::new(
609                pruned_banks_sender,
610            ))));
611        }
612        pruned_banks_receiver
613    }
614
615    pub fn join(self) -> thread::Result<()> {
616        self.t_background.join()
617    }
618
619    /// Returns an object to query/manage the status of ABS
620    pub fn status(&self) -> &AbsStatus {
621        &self.status
622    }
623}
624
625/// Query and manage the status of AccountsBackgroundService
626#[derive(Debug, Clone)]
627pub struct AbsStatus {
628    /// Flag to query if ABS is running
629    is_running: Arc<AtomicBool>,
630    /// Flag to set to stop ABS
631    stop: Arc<AtomicBool>,
632}
633
634impl AbsStatus {
635    /// Returns if ABS is running
636    pub fn is_running(&self) -> bool {
637        self.is_running.load(Ordering::Relaxed)
638    }
639
640    /// Raises the flag for ABS to stop
641    pub fn stop(&self) {
642        self.stop.store(true, Ordering::Relaxed)
643    }
644
645    #[cfg(feature = "dev-context-only-utils")]
646    pub fn new_for_tests() -> Self {
647        Self {
648            is_running: Arc::new(AtomicBool::new(false)),
649            stop: Arc::new(AtomicBool::new(false)),
650        }
651    }
652}
653
654/// Get the SnapshotKind from a given SnapshotRequest
655#[must_use]
656fn new_snapshot_kind(snapshot_request: &SnapshotRequest) -> Option<SnapshotKind> {
657    match snapshot_request.request_kind {
658        SnapshotRequestKind::FullSnapshot => Some(SnapshotKind::Archive(SnapshotArchiveKind::Full)),
659        SnapshotRequestKind::IncrementalSnapshot => {
660            if let Some(latest_full_snapshot_slot) = snapshot_request
661                .snapshot_root_bank
662                .rc
663                .accounts
664                .accounts_db
665                .latest_full_snapshot_slot()
666            {
667                Some(SnapshotKind::Archive(SnapshotArchiveKind::Incremental(
668                    latest_full_snapshot_slot,
669                )))
670            } else {
671                warn!(
672                    "Ignoring IncrementalSnapshot request for slot {} because there is no latest \
673                     full snapshot",
674                    snapshot_request.snapshot_root_bank.slot()
675                );
676                None
677            }
678        }
679        SnapshotRequestKind::FastbootSnapshot => Some(SnapshotKind::Fastboot),
680    }
681}
682
683/// Compare snapshot requests; used to pick the highest priority request to handle.
684///
685/// Priority, from highest to lowest:
686/// - Epoch Accounts Hash
687/// - Full Snapshot
688/// - Incremental Snapshot
689///
690/// If two requests of the same kind are being compared, their bank slots are the tiebreaker.
691#[must_use]
692fn cmp_requests_by_priority(a: &SnapshotRequest, b: &SnapshotRequest) -> cmp::Ordering {
693    let slot_a = a.snapshot_root_bank.slot();
694    let slot_b = b.snapshot_root_bank.slot();
695    cmp_snapshot_request_kinds_by_priority(&a.request_kind, &b.request_kind)
696        .then(slot_a.cmp(&slot_b))
697}
698
699/// Compare snapshot request kinds by priority
700///
701/// Priority, from highest to lowest:
702/// - Full Snapshot
703/// - Incremental Snapshot
704/// - Fastboot Snapshot
705#[must_use]
706fn cmp_snapshot_request_kinds_by_priority(
707    a: &SnapshotRequestKind,
708    b: &SnapshotRequestKind,
709) -> cmp::Ordering {
710    use {
711        SnapshotRequestKind as Kind,
712        cmp::Ordering::{Equal, Greater, Less},
713    };
714    match (a, b) {
715        (Kind::FullSnapshot, Kind::FullSnapshot) => Equal,
716        (Kind::FullSnapshot, Kind::IncrementalSnapshot) => Greater,
717        (Kind::FullSnapshot, Kind::FastbootSnapshot) => Greater,
718        (Kind::IncrementalSnapshot, Kind::FullSnapshot) => Less,
719        (Kind::IncrementalSnapshot, Kind::IncrementalSnapshot) => Equal,
720        (Kind::IncrementalSnapshot, Kind::FastbootSnapshot) => Greater,
721        (Kind::FastbootSnapshot, Kind::FullSnapshot) => Less,
722        (Kind::FastbootSnapshot, Kind::IncrementalSnapshot) => Less,
723        (Kind::FastbootSnapshot, Kind::FastbootSnapshot) => Equal,
724    }
725}
726
727#[cfg(test)]
728mod test {
729    use {
730        super::*, crate::genesis_utils::create_genesis_config,
731        agave_snapshots::snapshot_config::SnapshotConfig, crossbeam_channel::bounded,
732        solana_account::AccountSharedData, solana_epoch_schedule::EpochSchedule,
733        solana_leader_schedule::SlotLeader, solana_pubkey::Pubkey,
734    };
735
736    #[test]
737    fn test_accounts_background_service_remove_dead_slots() {
738        let genesis = create_genesis_config(10);
739        let bank0 = Arc::new(Bank::new_for_tests(&genesis.genesis_config));
740        let (pruned_banks_sender, pruned_banks_receiver) = bounded(1024);
741        let pruned_banks_request_handler = PrunedBanksRequestHandler {
742            pruned_banks_receiver,
743        };
744
745        // Store an account in slot 0
746        let account_key = Pubkey::new_unique();
747        bank0.store_account(
748            &account_key,
749            &AccountSharedData::new(264, 0, &Pubkey::default()),
750        );
751        assert!(bank0.get_account(&account_key).is_some());
752        pruned_banks_sender.send((0, 0)).unwrap();
753
754        assert!(!bank0.rc.accounts.scan_slot(0, |_| Some(())).is_empty());
755
756        pruned_banks_request_handler.remove_dead_slots(&bank0, &mut 0, &mut 0);
757
758        assert!(bank0.rc.accounts.scan_slot(0, |_| Some(())).is_empty());
759    }
760
761    /// Ensure that unhandled snapshot requests are properly re-enqueued or dropped
762    ///
763    /// The snapshot request handler should be flexible and handle re-queueing unhandled snapshot
764    /// requests, if those unhandled requests are for slots GREATER-THAN the last request handled.
765    #[test]
766    fn test_get_next_snapshot_request() {
767        // These constants were picked to ensure the desired snapshot requests were sent to the
768        // channel.  Ensure there are multiple requests of each kind.
769        const SLOTS_PER_EPOCH: Slot = 400;
770        const FULL_SNAPSHOT_INTERVAL: Slot = 80;
771        const INCREMENTAL_SNAPSHOT_INTERVAL: Slot = 30;
772        const FASTBOOT_SNAPSHOT_INTERVAL: Slot = 45;
773
774        // This would typically configure the snapshot controller, but since `set_root` is never
775        // called, the snapshot controller is never invoked. The default configuration suffices
776        // as it does not affect the test behavior.
777        let snapshot_config = SnapshotConfig::default();
778
779        let pending_snapshot_packages = Arc::new(Mutex::new(PendingSnapshotPackages::default()));
780        let (snapshot_request_sender, snapshot_request_receiver) = bounded(1024);
781        let snapshot_controller = Arc::new(SnapshotController::new(
782            snapshot_request_sender.clone(),
783            snapshot_config,
784            0,
785        ));
786        let snapshot_request_handler = SnapshotRequestHandler {
787            snapshot_controller,
788            snapshot_request_receiver,
789            pending_snapshot_packages,
790        };
791
792        let send_snapshot_request = |snapshot_root_bank, request_kind| {
793            let snapshot_request = SnapshotRequest {
794                snapshot_root_bank,
795                status_cache_slot_deltas: Vec::default(),
796                request_kind,
797                enqueued: Instant::now(),
798            };
799            snapshot_request_sender.send(snapshot_request).unwrap();
800        };
801
802        let mut genesis_config_info = create_genesis_config(10);
803        genesis_config_info.genesis_config.epoch_schedule =
804            EpochSchedule::custom(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH, false);
805        let (mut bank, _bank_forks) = Bank::new_for_tests(&genesis_config_info.genesis_config)
806            .wrap_with_bank_forks_for_tests();
807
808        // We need to get and set accounts-db's latest full snapshot slot to test
809        // get_next_snapshot_request().  To workaround potential borrowing issues
810        // caused by make_banks() below, Arc::clone bank0 and add helper functions.
811        let bank0 = bank.clone();
812        fn latest_full_snapshot_slot(bank: &Bank) -> Option<Slot> {
813            bank.rc.accounts.accounts_db.latest_full_snapshot_slot()
814        }
815        fn set_latest_full_snapshot_slot(bank: &Bank, slot: Slot) {
816            bank.rc
817                .accounts
818                .accounts_db
819                .set_latest_full_snapshot_slot(slot);
820        }
821
822        // Create new banks and send snapshot requests so that the following requests will be in
823        // the channel before handling the requests:
824        //
825        // full          80
826        // incremental   90
827        // incremental  120
828        // fastboot     135
829        // incremental  150
830        // full         160
831        // incremental  180
832        // incremental  210
833        // fastboot     225
834        // full         240 <-- handled 1st
835        // incremental  270
836        // incremental  300 <-- handled 2nd
837        // fastboot     315 <-- handled last
838        // Also, incremental snapshots before slot 240 (the first full snapshot handled), will
839        // actually be skipped since the latest full snapshot slot will be `None`.
840        let mut make_banks = |num_banks| {
841            for _ in 0..num_banks {
842                let slot = bank.slot() + 1;
843                bank = Arc::new(Bank::new_from_parent(
844                    bank.clone(),
845                    SlotLeader::new_unique(),
846                    slot,
847                ));
848
849                // Since we're not using `BankForks::set_root()`, we have to handle sending the
850                // correct snapshot requests ourself.
851                if bank.block_height().is_multiple_of(FULL_SNAPSHOT_INTERVAL) {
852                    send_snapshot_request(Arc::clone(&bank), SnapshotRequestKind::FullSnapshot);
853                } else if bank
854                    .block_height()
855                    .is_multiple_of(INCREMENTAL_SNAPSHOT_INTERVAL)
856                {
857                    send_snapshot_request(
858                        Arc::clone(&bank),
859                        SnapshotRequestKind::IncrementalSnapshot,
860                    );
861                } else if bank
862                    .block_height()
863                    .is_multiple_of(FASTBOOT_SNAPSHOT_INTERVAL)
864                {
865                    send_snapshot_request(Arc::clone(&bank), SnapshotRequestKind::FastbootSnapshot);
866                }
867            }
868        };
869        make_banks(318);
870
871        // Ensure the full snapshot from slot 240 is handled 1st
872        // (the older full snapshots are skipped and dropped)
873        assert_eq!(latest_full_snapshot_slot(&bank0), None);
874        let (snapshot_request, ..) = snapshot_request_handler
875            .get_next_snapshot_request()
876            .unwrap();
877        assert_eq!(
878            snapshot_request.request_kind,
879            SnapshotRequestKind::FullSnapshot
880        );
881        assert_eq!(snapshot_request.snapshot_root_bank.slot(), 240);
882        set_latest_full_snapshot_slot(&bank0, 240);
883
884        // Ensure the incremental snapshot from slot 300 is handled 2nd
885        // (the older incremental snapshots are skipped and dropped)
886        assert_eq!(latest_full_snapshot_slot(&bank0), Some(240));
887        let (snapshot_request, ..) = snapshot_request_handler
888            .get_next_snapshot_request()
889            .unwrap();
890        assert_eq!(
891            snapshot_request.request_kind,
892            SnapshotRequestKind::IncrementalSnapshot
893        );
894        assert_eq!(snapshot_request.snapshot_root_bank.slot(), 300);
895
896        // Ensure the fastboot snapshot from slot 315 is handled last
897        // (the older fastboot snapshots are skipped and dropped)
898        assert_eq!(latest_full_snapshot_slot(&bank0), Some(240));
899        let (snapshot_request, ..) = snapshot_request_handler
900            .get_next_snapshot_request()
901            .unwrap();
902        assert_eq!(
903            snapshot_request.request_kind,
904            SnapshotRequestKind::FastbootSnapshot
905        );
906        assert_eq!(snapshot_request.snapshot_root_bank.slot(), 315);
907
908        // And now ensure the snapshot request channel is empty!
909        assert_eq!(latest_full_snapshot_slot(&bank0), Some(240));
910        assert!(
911            snapshot_request_handler
912                .get_next_snapshot_request()
913                .is_none()
914        );
915    }
916
917    /// Ensure that we can prune banks with the same slot (if they were on different forks)
918    #[test]
919    fn test_pruned_banks_request_handler_handle_request() {
920        let (pruned_banks_sender, pruned_banks_receiver) = bounded(1024);
921        let pruned_banks_request_handler = PrunedBanksRequestHandler {
922            pruned_banks_receiver,
923        };
924        let genesis_config_info = create_genesis_config(10);
925        let bank = Bank::new_for_tests(&genesis_config_info.genesis_config);
926        bank.rc.accounts.accounts_db.enable_bank_drop_callback();
927        bank.set_callback(Some(Box::new(SendDroppedBankCallback::new(
928            pruned_banks_sender,
929        ))));
930
931        let (fork0_bank0, bank_forks) = bank.wrap_with_bank_forks_for_tests();
932        let fork0_bank1 = Arc::new(Bank::new_from_parent(
933            fork0_bank0.clone(),
934            SlotLeader::new_unique(),
935            fork0_bank0.slot() + 1,
936        ));
937        let fork1_bank1 = Arc::new(Bank::new_from_parent(
938            fork0_bank0.clone(),
939            SlotLeader::new_unique(),
940            fork0_bank0.slot() + 1,
941        ));
942        let fork2_bank1 = Arc::new(Bank::new_from_parent(
943            fork0_bank0.clone(),
944            SlotLeader::new_unique(),
945            fork0_bank0.slot() + 1,
946        ));
947        let fork0_bank2 = Arc::new(Bank::new_from_parent(
948            fork0_bank1.clone(),
949            SlotLeader::new_unique(),
950            fork0_bank1.slot() + 1,
951        ));
952        let fork1_bank2 = Arc::new(Bank::new_from_parent(
953            fork1_bank1.clone(),
954            SlotLeader::new_unique(),
955            fork1_bank1.slot() + 1,
956        ));
957        let fork0_bank3 = Arc::new(Bank::new_from_parent(
958            fork0_bank2.clone(),
959            SlotLeader::new_unique(),
960            fork0_bank2.slot() + 1,
961        ));
962        let fork3_bank3 = Arc::new(Bank::new_from_parent(
963            fork0_bank2.clone(),
964            SlotLeader::new_unique(),
965            fork0_bank2.slot() + 1,
966        ));
967        fork0_bank3.squash();
968
969        drop(fork3_bank3);
970        drop(fork1_bank2);
971        drop(fork0_bank2);
972        drop(fork1_bank1);
973        drop(fork2_bank1);
974        drop(fork0_bank1);
975        drop(fork0_bank0);
976        drop(bank_forks);
977        let num_banks_purged = pruned_banks_request_handler.handle_request(&fork0_bank3);
978        assert_eq!(num_banks_purged, 7);
979    }
980
981    #[test]
982    fn test_cmp_snapshot_request_kinds_by_priority() {
983        use cmp::Ordering::{Equal, Greater, Less};
984        for (snapshot_request_kind_a, snapshot_request_kind_b, expected_result) in [
985            (
986                SnapshotRequestKind::FullSnapshot,
987                SnapshotRequestKind::FullSnapshot,
988                Equal,
989            ),
990            (
991                SnapshotRequestKind::FullSnapshot,
992                SnapshotRequestKind::IncrementalSnapshot,
993                Greater,
994            ),
995            (
996                SnapshotRequestKind::FullSnapshot,
997                SnapshotRequestKind::FastbootSnapshot,
998                Greater,
999            ),
1000            (
1001                SnapshotRequestKind::IncrementalSnapshot,
1002                SnapshotRequestKind::FullSnapshot,
1003                Less,
1004            ),
1005            (
1006                SnapshotRequestKind::IncrementalSnapshot,
1007                SnapshotRequestKind::IncrementalSnapshot,
1008                Equal,
1009            ),
1010            (
1011                SnapshotRequestKind::IncrementalSnapshot,
1012                SnapshotRequestKind::FastbootSnapshot,
1013                Greater,
1014            ),
1015            (
1016                SnapshotRequestKind::FastbootSnapshot,
1017                SnapshotRequestKind::FullSnapshot,
1018                Less,
1019            ),
1020            (
1021                SnapshotRequestKind::FastbootSnapshot,
1022                SnapshotRequestKind::IncrementalSnapshot,
1023                Less,
1024            ),
1025            (
1026                SnapshotRequestKind::FastbootSnapshot,
1027                SnapshotRequestKind::FastbootSnapshot,
1028                Equal,
1029            ),
1030        ] {
1031            let actual_result = cmp_snapshot_request_kinds_by_priority(
1032                &snapshot_request_kind_a,
1033                &snapshot_request_kind_b,
1034            );
1035            assert_eq!(expected_result, actual_result);
1036        }
1037    }
1038}