Co-authored-by: Carl Lin <carl@solana.com>
This commit is contained in:
		| @@ -100,8 +100,23 @@ impl SnapshotRequestHandler { | |||||||
|  |  | ||||||
|                 let mut flush_accounts_cache_time = Measure::start("flush_accounts_cache_time"); |                 let mut flush_accounts_cache_time = Measure::start("flush_accounts_cache_time"); | ||||||
|                 if accounts_db_caching_enabled { |                 if accounts_db_caching_enabled { | ||||||
|                     // Force flush all the roots from the cache so that the snapshot can be taken. |                     // Forced cache flushing MUST flush all roots <= snapshot_root_bank.slot(). | ||||||
|  |                     // That's because `snapshot_root_bank.slot()` must be root at this point, | ||||||
|  |                     // and contains relevant updates because each bank has at least 1 account update due | ||||||
|  |                     // to sysvar maintenance. Otherwise, this would cause missing storages in the snapshot | ||||||
|                     snapshot_root_bank.force_flush_accounts_cache(); |                     snapshot_root_bank.force_flush_accounts_cache(); | ||||||
|  |                     // Ensure all roots <= `self.slot()` have been flushed. | ||||||
|  |                     // Note `max_flush_root` could be larger than self.slot() if there are | ||||||
|  |                     // `> MAX_CACHE_SLOT` cached and rooted slots which triggered earlier flushes. | ||||||
|  |                     assert!( | ||||||
|  |                         snapshot_root_bank.slot() | ||||||
|  |                             <= snapshot_root_bank | ||||||
|  |                                 .rc | ||||||
|  |                                 .accounts | ||||||
|  |                                 .accounts_db | ||||||
|  |                                 .accounts_cache | ||||||
|  |                                 .fetch_max_flush_root() | ||||||
|  |                     ); | ||||||
|                 } |                 } | ||||||
|                 flush_accounts_cache_time.stop(); |                 flush_accounts_cache_time.stop(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2996,27 +2996,31 @@ impl AccountsDB { | |||||||
|         self.accounts_cache.report_size(); |         self.accounts_cache.report_size(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // `force_flush` flushes all the cached roots `<= max_clean_root`. It also then |     // `force_flush` flushes all the cached roots `<= requested_flush_root`. It also then | ||||||
|     // flushes: |     // flushes: | ||||||
|     // 1) Any remaining roots if there are > MAX_CACHE_SLOTS remaining slots in the cache, |     // 1) Any remaining roots if there are > MAX_CACHE_SLOTS remaining slots in the cache, | ||||||
|     // 2) It there are still > MAX_CACHE_SLOTS remaining slots in the cache, the excess |     // 2) It there are still > MAX_CACHE_SLOTS remaining slots in the cache, the excess | ||||||
|     // unrooted slots |     // unrooted slots | ||||||
|     pub fn flush_accounts_cache(&self, force_flush: bool, max_clean_root: Option<Slot>) { |     pub fn flush_accounts_cache(&self, force_flush: bool, requested_flush_root: Option<Slot>) { | ||||||
|         #[cfg(not(test))] |         #[cfg(not(test))] | ||||||
|         assert!(max_clean_root.is_some()); |         assert!(requested_flush_root.is_some()); | ||||||
|  |  | ||||||
|         if !force_flush && self.accounts_cache.num_slots() <= MAX_CACHE_SLOTS { |         if !force_flush && self.accounts_cache.num_slots() <= MAX_CACHE_SLOTS { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Flush only the roots <= max_clean_root, so that snapshotting has all |         // Flush only the roots <= requested_flush_root, so that snapshotting has all | ||||||
|         // the relevant roots in storage. |         // the relevant roots in storage. | ||||||
|         let mut flush_roots_elapsed = Measure::start("flush_roots_elapsed"); |         let mut flush_roots_elapsed = Measure::start("flush_roots_elapsed"); | ||||||
|         let mut account_bytes_saved = 0; |         let mut account_bytes_saved = 0; | ||||||
|         let mut num_accounts_saved = 0; |         let mut num_accounts_saved = 0; | ||||||
|  |  | ||||||
|  |         // Note even if force_flush is false, we will still flush all roots <= the | ||||||
|  |         // given `requested_flush_root`, even if some of the later roots cannot be used for | ||||||
|  |         // cleaning due to an ongoing scan | ||||||
|         let (total_new_cleaned_roots, num_cleaned_roots_flushed) = self |         let (total_new_cleaned_roots, num_cleaned_roots_flushed) = self | ||||||
|             .flush_rooted_accounts_cache( |             .flush_rooted_accounts_cache( | ||||||
|                 max_clean_root, |                 requested_flush_root, | ||||||
|                 Some((&mut account_bytes_saved, &mut num_accounts_saved)), |                 Some((&mut account_bytes_saved, &mut num_accounts_saved)), | ||||||
|             ); |             ); | ||||||
|         flush_roots_elapsed.stop(); |         flush_roots_elapsed.stop(); | ||||||
| @@ -3030,7 +3034,7 @@ impl AccountsDB { | |||||||
|             if self.accounts_cache.num_slots() > MAX_CACHE_SLOTS { |             if self.accounts_cache.num_slots() > MAX_CACHE_SLOTS { | ||||||
|                 // Start by flushing the roots |                 // Start by flushing the roots | ||||||
|                 // |                 // | ||||||
|                 // Cannot do any cleaning on roots past `max_clean_root` because future |                 // Cannot do any cleaning on roots past `requested_flush_root` because future | ||||||
|                 // snapshots may need updates from those later slots, hence we pass `None` |                 // snapshots may need updates from those later slots, hence we pass `None` | ||||||
|                 // for `should_clean`. |                 // for `should_clean`. | ||||||
|                 self.flush_rooted_accounts_cache(None, None) |                 self.flush_rooted_accounts_cache(None, None) | ||||||
| @@ -3093,16 +3097,14 @@ impl AccountsDB { | |||||||
|  |  | ||||||
|     fn flush_rooted_accounts_cache( |     fn flush_rooted_accounts_cache( | ||||||
|         &self, |         &self, | ||||||
|         mut max_flush_root: Option<Slot>, |         requested_flush_root: Option<Slot>, | ||||||
|         should_clean: Option<(&mut usize, &mut usize)>, |         should_clean: Option<(&mut usize, &mut usize)>, | ||||||
|     ) -> (usize, usize) { |     ) -> (usize, usize) { | ||||||
|         if should_clean.is_some() { |         let max_clean_root = should_clean.as_ref().and_then(|_| { | ||||||
|             max_flush_root = self.max_clean_root(max_flush_root); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|             // If there is a long running scan going on, this could prevent any cleaning |             // If there is a long running scan going on, this could prevent any cleaning | ||||||
|         // past `max_flush_root`. |             // based on updates from slots > `max_clean_root`. | ||||||
|         let cached_roots: BTreeSet<Slot> = self.accounts_cache.clear_roots(max_flush_root); |             self.max_clean_root(requested_flush_root) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         // Use HashMap because HashSet doesn't provide Entry api |         // Use HashMap because HashSet doesn't provide Entry api | ||||||
|         let mut written_accounts = HashMap::new(); |         let mut written_accounts = HashMap::new(); | ||||||
| @@ -3112,7 +3114,6 @@ impl AccountsDB { | |||||||
|         let mut should_flush_f = should_clean.map(|(account_bytes_saved, num_accounts_saved)| { |         let mut should_flush_f = should_clean.map(|(account_bytes_saved, num_accounts_saved)| { | ||||||
|             move |&pubkey: &Pubkey, account: &Account| { |             move |&pubkey: &Pubkey, account: &Account| { | ||||||
|                 use std::collections::hash_map::Entry::{Occupied, Vacant}; |                 use std::collections::hash_map::Entry::{Occupied, Vacant}; | ||||||
|  |  | ||||||
|                 let should_flush = match written_accounts.entry(pubkey) { |                 let should_flush = match written_accounts.entry(pubkey) { | ||||||
|                     Vacant(vacant_entry) => { |                     Vacant(vacant_entry) => { | ||||||
|                         vacant_entry.insert(()); |                         vacant_entry.insert(()); | ||||||
| @@ -3130,11 +3131,27 @@ impl AccountsDB { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         // Always flush up to `requested_flush_root`, which is necessary for things like snapshotting. | ||||||
|  |         let cached_roots: BTreeSet<Slot> = self.accounts_cache.clear_roots(requested_flush_root); | ||||||
|  |  | ||||||
|         // Iterate from highest to lowest so that we don't need to flush earlier |         // Iterate from highest to lowest so that we don't need to flush earlier | ||||||
|         // outdated updates in earlier roots |         // outdated updates in earlier roots | ||||||
|         let mut num_roots_flushed = 0; |         let mut num_roots_flushed = 0; | ||||||
|         for &root in cached_roots.iter().rev() { |         for &root in cached_roots.iter().rev() { | ||||||
|             if self.flush_slot_cache(root, should_flush_f.as_mut()) { |             let should_flush_f = if let Some(max_clean_root) = max_clean_root { | ||||||
|  |                 if root > max_clean_root { | ||||||
|  |                     // Only if the root is greater than the `max_clean_root` do we | ||||||
|  |                     // have to prevent cleaning, otherwise, just default to `should_flush_f` | ||||||
|  |                     // for any slots <= `max_clean_root` | ||||||
|  |                     None | ||||||
|  |                 } else { | ||||||
|  |                     should_flush_f.as_mut() | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 should_flush_f.as_mut() | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             if self.flush_slot_cache(root, should_flush_f) { | ||||||
|                 num_roots_flushed += 1; |                 num_roots_flushed += 1; | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -4390,7 +4407,7 @@ pub mod tests { | |||||||
|     use std::{ |     use std::{ | ||||||
|         iter::FromIterator, |         iter::FromIterator, | ||||||
|         str::FromStr, |         str::FromStr, | ||||||
|         thread::{sleep, Builder}, |         thread::{self, sleep, Builder, JoinHandle}, | ||||||
|         time::Duration, |         time::Duration, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -7431,54 +7448,37 @@ pub mod tests { | |||||||
|             .is_none()); |             .is_none()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     struct ScanTracker { | ||||||
|     fn test_scan_flush_accounts_cache_then_clean_drop() { |         t_scan: JoinHandle<()>, | ||||||
|         let caching_enabled = true; |         exit: Arc<AtomicBool>, | ||||||
|         let db = Arc::new(AccountsDB::new_with_config( |     } | ||||||
|             Vec::new(), |  | ||||||
|             &ClusterType::Development, |     impl ScanTracker { | ||||||
|             HashSet::new(), |         fn exit(self) -> thread::Result<()> { | ||||||
|             caching_enabled, |             self.exit.store(true, Ordering::Relaxed); | ||||||
|         )); |             self.t_scan.join() | ||||||
|         let db_ = db.clone(); |         } | ||||||
|         let account_key = Pubkey::new_unique(); |     } | ||||||
|         let account_key2 = Pubkey::new_unique(); |  | ||||||
|         let zero_lamport_account = Account::new(0, 0, &Account::default().owner); |     fn setup_scan( | ||||||
|         let slot1_account = Account::new(1, 1, &Account::default().owner); |         db: Arc<AccountsDB>, | ||||||
|         let slot2_account = Account::new(2, 1, &Account::default().owner); |         scan_ancestors: Arc<Ancestors>, | ||||||
|  |         stall_key: Pubkey, | ||||||
|  |     ) -> ScanTracker { | ||||||
|         let exit = Arc::new(AtomicBool::new(false)); |         let exit = Arc::new(AtomicBool::new(false)); | ||||||
|         let exit_ = exit.clone(); |         let exit_ = exit.clone(); | ||||||
|         let ready = Arc::new(AtomicBool::new(false)); |         let ready = Arc::new(AtomicBool::new(false)); | ||||||
|         let ready_ = ready.clone(); |         let ready_ = ready.clone(); | ||||||
|  |  | ||||||
|         /* |  | ||||||
|             Store zero lamport account into slots 0, 1, 2 where |  | ||||||
|             root slots are 0, 2, and slot 1 is unrooted. |  | ||||||
|                                     0 (root) |  | ||||||
|                                 /        \ |  | ||||||
|                               1            2 (root) |  | ||||||
|         */ |  | ||||||
|         db.store_cached(0, &[(&account_key, &zero_lamport_account)]); |  | ||||||
|         db.store_cached(1, &[(&account_key, &slot1_account)]); |  | ||||||
|         db.store_cached(2, &[(&account_key, &slot2_account)]); |  | ||||||
|         // Fodder for the scan so that the lock on `account_key` is not held |  | ||||||
|         db.store_cached(2, &[(&account_key2, &slot2_account)]); |  | ||||||
|         db.get_accounts_delta_hash(0); |  | ||||||
|         db.add_root(0); |  | ||||||
|         let max_scan_root = 0; |  | ||||||
|         let scan_ancestors: Arc<Ancestors> = Arc::new(vec![(0, 1), (1, 1)].into_iter().collect()); |  | ||||||
|         let scan_ancestors_ = scan_ancestors.clone(); |  | ||||||
|         let t_scan = Builder::new() |         let t_scan = Builder::new() | ||||||
|             .name("scan".to_string()) |             .name("scan".to_string()) | ||||||
|             .spawn(move || { |             .spawn(move || { | ||||||
|                 db_.scan_accounts( |                 db.scan_accounts( | ||||||
|                     &scan_ancestors_, |                     &scan_ancestors, | ||||||
|                     |_collector: &mut Vec<(Pubkey, Account)>, maybe_account| { |                     |_collector: &mut Vec<(Pubkey, Account)>, maybe_account| { | ||||||
|                         ready_.store(true, Ordering::Relaxed); |                         ready_.store(true, Ordering::Relaxed); | ||||||
|                         if let Some((pubkey, _, _)) = maybe_account { |                         if let Some((pubkey, _, _)) = maybe_account { | ||||||
|                             // Do the wait on account_key2, because clean is happening |                             if *pubkey == stall_key { | ||||||
|                             // on account_key1's index and we don't want to block the clean. |  | ||||||
|                             if *pubkey == account_key2 { |  | ||||||
|                                 loop { |                                 loop { | ||||||
|                                     if exit_.load(Ordering::Relaxed) { |                                     if exit_.load(Ordering::Relaxed) { | ||||||
|                                         break; |                                         break; | ||||||
| @@ -7498,15 +7498,68 @@ pub mod tests { | |||||||
|             sleep(Duration::from_millis(10)); |             sleep(Duration::from_millis(10)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Add a new root 2 |         ScanTracker { t_scan, exit } | ||||||
|         db.get_accounts_delta_hash(2); |     } | ||||||
|         db.add_root(2); |  | ||||||
|  |  | ||||||
|         // Flush the cache, slot 1 should remain in the cache, everything else should be flushed |     #[test] | ||||||
|         db.flush_accounts_cache(true, None); |     fn test_scan_flush_accounts_cache_then_clean_drop() { | ||||||
|  |         let caching_enabled = true; | ||||||
|  |         let db = Arc::new(AccountsDB::new_with_config( | ||||||
|  |             Vec::new(), | ||||||
|  |             &ClusterType::Development, | ||||||
|  |             HashSet::new(), | ||||||
|  |             caching_enabled, | ||||||
|  |         )); | ||||||
|  |         let account_key = Pubkey::new_unique(); | ||||||
|  |         let account_key2 = Pubkey::new_unique(); | ||||||
|  |         let zero_lamport_account = Account::new(0, 0, &Account::default().owner); | ||||||
|  |         let slot1_account = Account::new(1, 1, &Account::default().owner); | ||||||
|  |         let slot2_account = Account::new(2, 1, &Account::default().owner); | ||||||
|  |  | ||||||
|  |         /* | ||||||
|  |             Store zero lamport account into slots 0, 1, 2 where | ||||||
|  |             root slots are 0, 2, and slot 1 is unrooted. | ||||||
|  |                                     0 (root) | ||||||
|  |                                 /        \ | ||||||
|  |                               1            2 (root) | ||||||
|  |         */ | ||||||
|  |         db.store_cached(0, &[(&account_key, &zero_lamport_account)]); | ||||||
|  |         db.store_cached(1, &[(&account_key, &slot1_account)]); | ||||||
|  |         // Fodder for the scan so that the lock on `account_key` is not held | ||||||
|  |         db.store_cached(1, &[(&account_key2, &slot1_account)]); | ||||||
|  |         db.store_cached(2, &[(&account_key, &slot2_account)]); | ||||||
|  |         db.get_accounts_delta_hash(0); | ||||||
|  |  | ||||||
|  |         let max_scan_root = 0; | ||||||
|  |         db.add_root(max_scan_root); | ||||||
|  |         let scan_ancestors: Arc<Ancestors> = Arc::new(vec![(0, 1), (1, 1)].into_iter().collect()); | ||||||
|  |         let scan_tracker = setup_scan(db.clone(), scan_ancestors.clone(), account_key2); | ||||||
|  |  | ||||||
|  |         // Add a new root 2 | ||||||
|  |         let new_root = 2; | ||||||
|  |         db.get_accounts_delta_hash(new_root); | ||||||
|  |         db.add_root(new_root); | ||||||
|  |  | ||||||
|  |         // Check that the scan is properly set up | ||||||
|  |         assert_eq!( | ||||||
|  |             db.accounts_index.min_ongoing_scan_root().unwrap(), | ||||||
|  |             max_scan_root | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // If we specify a requested_flush_root == 2, then `slot 2 <= max_flush_slot` will | ||||||
|  |         // be flushed even though `slot 2 > max_scan_root`. The unrooted slot 1 should | ||||||
|  |         // remain in the cache | ||||||
|  |         db.flush_accounts_cache(true, Some(new_root)); | ||||||
|         assert_eq!(db.accounts_cache.num_slots(), 1); |         assert_eq!(db.accounts_cache.num_slots(), 1); | ||||||
|         assert!(db.accounts_cache.slot_cache(1).is_some()); |         assert!(db.accounts_cache.slot_cache(1).is_some()); | ||||||
|  |  | ||||||
|  |         // Intra cache cleaning should not clean the entry for `account_key` from slot 0, | ||||||
|  |         // even though it was updated in slot `2` because of the ongoing scan | ||||||
|  |         let account = db | ||||||
|  |             .do_load(&Ancestors::default(), &account_key, Some(0)) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(account.0.lamports, zero_lamport_account.lamports); | ||||||
|  |  | ||||||
|         // Run clean, unrooted slot 1 should not be purged, and still readable from the cache, |         // Run clean, unrooted slot 1 should not be purged, and still readable from the cache, | ||||||
|         // because we're still doing a scan on it. |         // because we're still doing a scan on it. | ||||||
|         db.clean_accounts(None); |         db.clean_accounts(None); | ||||||
| @@ -7517,8 +7570,7 @@ pub mod tests { | |||||||
|  |  | ||||||
|         // When the scan is over, clean should not panic and should not purge something |         // When the scan is over, clean should not panic and should not purge something | ||||||
|         // still in the cache. |         // still in the cache. | ||||||
|         exit.store(true, Ordering::Relaxed); |         scan_tracker.exit().unwrap(); | ||||||
|         t_scan.join().unwrap(); |  | ||||||
|         db.clean_accounts(None); |         db.clean_accounts(None); | ||||||
|         let account = db |         let account = db | ||||||
|             .do_load(&scan_ancestors, &account_key, Some(max_scan_root)) |             .do_load(&scan_ancestors, &account_key, Some(max_scan_root)) | ||||||
| @@ -7585,26 +7637,49 @@ pub mod tests { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn setup_accounts_db_cache_clean(num_slots: usize) -> (AccountsDB, Vec<Pubkey>, Vec<Slot>) { |     fn setup_accounts_db_cache_clean( | ||||||
|  |         num_slots: usize, | ||||||
|  |         scan_slot: Option<Slot>, | ||||||
|  |     ) -> (Arc<AccountsDB>, Vec<Pubkey>, Vec<Slot>, Option<ScanTracker>) { | ||||||
|         let caching_enabled = true; |         let caching_enabled = true; | ||||||
|         let accounts_db = AccountsDB::new_with_config( |         let accounts_db = Arc::new(AccountsDB::new_with_config( | ||||||
|             Vec::new(), |             Vec::new(), | ||||||
|             &ClusterType::Development, |             &ClusterType::Development, | ||||||
|             HashSet::new(), |             HashSet::new(), | ||||||
|             caching_enabled, |             caching_enabled, | ||||||
|         ); |         )); | ||||||
|         let slots: Vec<_> = (0..num_slots as Slot).into_iter().collect(); |         let slots: Vec<_> = (0..num_slots as Slot).into_iter().collect(); | ||||||
|  |         let stall_slot = num_slots as Slot; | ||||||
|  |         let scan_stall_key = Pubkey::new_unique(); | ||||||
|         let keys: Vec<Pubkey> = std::iter::repeat_with(Pubkey::new_unique) |         let keys: Vec<Pubkey> = std::iter::repeat_with(Pubkey::new_unique) | ||||||
|             .take(num_slots) |             .take(num_slots) | ||||||
|             .collect(); |             .collect(); | ||||||
|  |         if scan_slot.is_some() { | ||||||
|  |             accounts_db.store_cached( | ||||||
|  |                 // Store it in a slot that isn't returned in `slots` | ||||||
|  |                 stall_slot, | ||||||
|  |                 &[(&scan_stall_key, &Account::new(1, 0, &Pubkey::default()))], | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // Store some subset of the keys in slots 0..num_slots |         // Store some subset of the keys in slots 0..num_slots | ||||||
|  |         let mut scan_tracker = None; | ||||||
|         for slot in &slots { |         for slot in &slots { | ||||||
|             for key in &keys[*slot as usize..] { |             for key in &keys[*slot as usize..] { | ||||||
|                 accounts_db.store_cached(*slot, &[(key, &Account::new(1, 0, &Pubkey::default()))]); |                 accounts_db.store_cached(*slot, &[(key, &Account::new(1, 0, &Pubkey::default()))]); | ||||||
|             } |             } | ||||||
|             accounts_db.add_root(*slot as Slot); |             accounts_db.add_root(*slot as Slot); | ||||||
|  |             if Some(*slot) == scan_slot { | ||||||
|  |                 let ancestors = Arc::new(vec![(stall_slot, 1), (*slot, 1)].into_iter().collect()); | ||||||
|  |                 scan_tracker = Some(setup_scan(accounts_db.clone(), ancestors, scan_stall_key)); | ||||||
|  |                 assert_eq!( | ||||||
|  |                     accounts_db.accounts_index.min_ongoing_scan_root().unwrap(), | ||||||
|  |                     *slot | ||||||
|  |                 ); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         accounts_db.accounts_cache.remove_slot(stall_slot); | ||||||
|  |  | ||||||
|         // If there's <= MAX_CACHE_SLOTS, no slots should be flushed |         // If there's <= MAX_CACHE_SLOTS, no slots should be flushed | ||||||
|         if accounts_db.accounts_cache.num_slots() <= MAX_CACHE_SLOTS { |         if accounts_db.accounts_cache.num_slots() <= MAX_CACHE_SLOTS { | ||||||
| @@ -7612,13 +7687,13 @@ pub mod tests { | |||||||
|             assert_eq!(accounts_db.accounts_cache.num_slots(), num_slots); |             assert_eq!(accounts_db.accounts_cache.num_slots(), num_slots); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         (accounts_db, keys, slots) |         (accounts_db, keys, slots, scan_tracker) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_accounts_db_cache_clean_dead_slots() { |     fn test_accounts_db_cache_clean_dead_slots() { | ||||||
|         let num_slots = 10; |         let num_slots = 10; | ||||||
|         let (accounts_db, keys, mut slots) = setup_accounts_db_cache_clean(num_slots); |         let (accounts_db, keys, mut slots, _) = setup_accounts_db_cache_clean(num_slots, None); | ||||||
|         let last_dead_slot = (num_slots - 1) as Slot; |         let last_dead_slot = (num_slots - 1) as Slot; | ||||||
|         assert_eq!(*slots.last().unwrap(), last_dead_slot); |         assert_eq!(*slots.last().unwrap(), last_dead_slot); | ||||||
|         let alive_slot = last_dead_slot as Slot + 1; |         let alive_slot = last_dead_slot as Slot + 1; | ||||||
| @@ -7685,7 +7760,7 @@ pub mod tests { | |||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_accounts_db_cache_clean() { |     fn test_accounts_db_cache_clean() { | ||||||
|         let (accounts_db, keys, slots) = setup_accounts_db_cache_clean(10); |         let (accounts_db, keys, slots, _) = setup_accounts_db_cache_clean(10, None); | ||||||
|  |  | ||||||
|         // If no `max_clean_root` is specified, cleaning should purge all flushed slots |         // If no `max_clean_root` is specified, cleaning should purge all flushed slots | ||||||
|         accounts_db.flush_accounts_cache(true, None); |         accounts_db.flush_accounts_cache(true, None); | ||||||
| @@ -7719,21 +7794,27 @@ pub mod tests { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn run_test_accounts_db_cache_clean_max_root(num_slots: usize, max_clean_root: Slot) { |     fn run_test_accounts_db_cache_clean_max_root( | ||||||
|         assert!(max_clean_root < (num_slots as Slot)); |         num_slots: usize, | ||||||
|         let (accounts_db, keys, slots) = setup_accounts_db_cache_clean(num_slots); |         requested_flush_root: Slot, | ||||||
|         let is_cache_at_limit = num_slots - max_clean_root as usize - 1 > MAX_CACHE_SLOTS; |         scan_root: Option<Slot>, | ||||||
|  |     ) { | ||||||
|  |         assert!(requested_flush_root < (num_slots as Slot)); | ||||||
|  |         let (accounts_db, keys, slots, scan_tracker) = | ||||||
|  |             setup_accounts_db_cache_clean(num_slots, scan_root); | ||||||
|  |         let is_cache_at_limit = num_slots - requested_flush_root as usize - 1 > MAX_CACHE_SLOTS; | ||||||
|  |  | ||||||
|         // If: |         // If: | ||||||
|         // 1) `max_clean_root` is specified, |         // 1) `requested_flush_root` is specified, | ||||||
|         // 2) not at the cache limit, i.e. `is_cache_at_limit == false`, then |         // 2) not at the cache limit, i.e. `is_cache_at_limit == false`, then | ||||||
|         // `flush_accounts_cache()` should clean and flushed only slots < max_clean_root, |         // `flush_accounts_cache()` should clean and flush only slots <= requested_flush_root, | ||||||
|         accounts_db.flush_accounts_cache(true, Some(max_clean_root)); |         accounts_db.flush_accounts_cache(true, Some(requested_flush_root)); | ||||||
|  |  | ||||||
|         if !is_cache_at_limit { |         if !is_cache_at_limit { | ||||||
|             // Should flush all slots between 0..=max_clean_root |             // Should flush all slots between 0..=requested_flush_root | ||||||
|             assert_eq!( |             assert_eq!( | ||||||
|                 accounts_db.accounts_cache.num_slots(), |                 accounts_db.accounts_cache.num_slots(), | ||||||
|                 slots.len() - max_clean_root as usize - 1 |                 slots.len() - requested_flush_root as usize - 1 | ||||||
|             ); |             ); | ||||||
|         } else { |         } else { | ||||||
|             // Otherwise, if we are at the cache limit, all roots will be flushed |             // Otherwise, if we are at the cache limit, all roots will be flushed | ||||||
| @@ -7747,9 +7828,9 @@ pub mod tests { | |||||||
|             .collect::<Vec<_>>(); |             .collect::<Vec<_>>(); | ||||||
|         uncleaned_roots.sort_unstable(); |         uncleaned_roots.sort_unstable(); | ||||||
|  |  | ||||||
|         let expected_max_clean_root = if !is_cache_at_limit { |         let expected_max_flushed_root = if !is_cache_at_limit { | ||||||
|             // Should flush all slots between 0..=max_clean_root |             // Should flush all slots between 0..=requested_flush_root | ||||||
|             max_clean_root |             requested_flush_root | ||||||
|         } else { |         } else { | ||||||
|             // Otherwise, if we are at the cache limit, all roots will be flushed |             // Otherwise, if we are at the cache limit, all roots will be flushed | ||||||
|             num_slots as Slot - 1 |             num_slots as Slot - 1 | ||||||
| @@ -7757,14 +7838,13 @@ pub mod tests { | |||||||
|  |  | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             uncleaned_roots, |             uncleaned_roots, | ||||||
|             slots[0..=expected_max_clean_root as usize].to_vec() |             slots[0..=expected_max_flushed_root as usize].to_vec() | ||||||
|         ); |         ); | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             accounts_db.accounts_cache.fetch_max_flush_root(), |             accounts_db.accounts_cache.fetch_max_flush_root(), | ||||||
|             expected_max_clean_root, |             expected_max_flushed_root, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         // Updates from slots > max_clean_root should still be flushed to storage |  | ||||||
|         for slot in &slots { |         for slot in &slots { | ||||||
|             let slot_accounts = accounts_db.scan_account_storage( |             let slot_accounts = accounts_db.scan_account_storage( | ||||||
|                 *slot as Slot, |                 *slot as Slot, | ||||||
| @@ -7774,7 +7854,9 @@ pub mod tests { | |||||||
|                             "When cache is at limit, all roots should have been flushed to storage" |                             "When cache is at limit, all roots should have been flushed to storage" | ||||||
|                         ); |                         ); | ||||||
|                     } |                     } | ||||||
|                     assert!(*slot > max_clean_root); |                     // All slots <= requested_flush_root should have been flushed, regardless | ||||||
|  |                     // of ongoing scans | ||||||
|  |                     assert!(*slot > requested_flush_root); | ||||||
|                     Some(*loaded_account.pubkey()) |                     Some(*loaded_account.pubkey()) | ||||||
|                 }, |                 }, | ||||||
|                 |slot_accounts: &DashSet<Pubkey>, loaded_account: LoadedAccount| { |                 |slot_accounts: &DashSet<Pubkey>, loaded_account: LoadedAccount| { | ||||||
| @@ -7782,7 +7864,7 @@ pub mod tests { | |||||||
|                     if !is_cache_at_limit { |                     if !is_cache_at_limit { | ||||||
|                         // Only true when the limit hasn't been reached and there are still |                         // Only true when the limit hasn't been reached and there are still | ||||||
|                         // slots left in the cache |                         // slots left in the cache | ||||||
|                         assert!(*slot <= max_clean_root); |                         assert!(*slot <= requested_flush_root); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|             ); |             ); | ||||||
| @@ -7795,56 +7877,93 @@ pub mod tests { | |||||||
|                     slot_accounts.into_iter().collect::<HashSet<Pubkey>>() |                     slot_accounts.into_iter().collect::<HashSet<Pubkey>>() | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|             if *slot >= max_clean_root { |  | ||||||
|                 // 1) If slot > `max_clean_root`, then  either: |             let expected_accounts = | ||||||
|                 //   a) If `is_cache_at_limit == true`, still in the cache |                 if *slot >= requested_flush_root || *slot >= scan_root.unwrap_or(Slot::MAX) { | ||||||
|                 //   b) if `is_cache_at_limit == false`, were not cleaned before being flushed to storage. |                     // 1) If slot > `requested_flush_root`, then  either: | ||||||
|  |                     //   a) If `is_cache_at_limit == false`, still in the cache | ||||||
|  |                     //   b) if `is_cache_at_limit == true`, were not cleaned before being flushed to storage. | ||||||
|                     // |                     // | ||||||
|                     // In both cases all the *original* updates at index `slot` were uncleaned and thus |                     // In both cases all the *original* updates at index `slot` were uncleaned and thus | ||||||
|                     // should be discoverable by this scan. |                     // should be discoverable by this scan. | ||||||
|                     // |                     // | ||||||
|                 // 2) If slot == `max_clean_root`, the slot was not cleaned before being flushed to storage, |                     // 2) If slot == `requested_flush_root`, the slot was not cleaned before being flushed to storage, | ||||||
|                     // so it also contains all the original updates. |                     // so it also contains all the original updates. | ||||||
|                 assert_eq!( |                     // | ||||||
|                     slot_accounts, |                     // 3) If *slot >= scan_root, then we should not clean it either | ||||||
|                     keys[*slot as usize..] |                     keys[*slot as usize..] | ||||||
|                         .iter() |                         .iter() | ||||||
|                         .cloned() |                         .cloned() | ||||||
|                         .collect::<HashSet<Pubkey>>() |                         .collect::<HashSet<Pubkey>>() | ||||||
|                 ); |  | ||||||
|                 } else { |                 } else { | ||||||
|                 // Slots less than `max_clean_root` were cleaned in the cache before being flushed |                     // Slots less than `requested_flush_root` and `scan_root` were cleaned in the cache before being flushed | ||||||
|                     // to storage, should only contain one account |                     // to storage, should only contain one account | ||||||
|                 assert_eq!( |  | ||||||
|                     slot_accounts, |  | ||||||
|                     std::iter::once(keys[*slot as usize]) |                     std::iter::once(keys[*slot as usize]) | ||||||
|                         .into_iter() |                         .into_iter() | ||||||
|                         .collect::<HashSet<Pubkey>>() |                         .collect::<HashSet<Pubkey>>() | ||||||
|                 ); |                 }; | ||||||
|  |  | ||||||
|  |             assert_eq!(slot_accounts, expected_accounts); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if let Some(scan_tracker) = scan_tracker { | ||||||
|  |             scan_tracker.exit().unwrap(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_accounts_db_cache_clean_max_root() { |     fn test_accounts_db_cache_clean_max_root() { | ||||||
|         let max_clean_root = 5; |         let requested_flush_root = 5; | ||||||
|         run_test_accounts_db_cache_clean_max_root(10, max_clean_root); |         run_test_accounts_db_cache_clean_max_root(10, requested_flush_root, None); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_accounts_db_cache_clean_max_root_with_scan() { | ||||||
|  |         let requested_flush_root = 5; | ||||||
|  |         run_test_accounts_db_cache_clean_max_root( | ||||||
|  |             10, | ||||||
|  |             requested_flush_root, | ||||||
|  |             Some(requested_flush_root - 1), | ||||||
|  |         ); | ||||||
|  |         run_test_accounts_db_cache_clean_max_root( | ||||||
|  |             10, | ||||||
|  |             requested_flush_root, | ||||||
|  |             Some(requested_flush_root + 1), | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_accounts_db_cache_clean_max_root_with_cache_limit_hit() { |     fn test_accounts_db_cache_clean_max_root_with_cache_limit_hit() { | ||||||
|         let max_clean_root = 5; |         let requested_flush_root = 5; | ||||||
|         // Test that if there are > MAX_CACHE_SLOTS in the cache after flush, then more roots |         // Test that if there are > MAX_CACHE_SLOTS in the cache after flush, then more roots | ||||||
|         // will be flushed |         // will be flushed | ||||||
|         run_test_accounts_db_cache_clean_max_root( |         run_test_accounts_db_cache_clean_max_root( | ||||||
|             MAX_CACHE_SLOTS + max_clean_root as usize + 2, |             MAX_CACHE_SLOTS + requested_flush_root as usize + 2, | ||||||
|             max_clean_root, |             requested_flush_root, | ||||||
|  |             None, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_accounts_db_cache_clean_max_root_with_cache_limit_hit_and_scan() { | ||||||
|  |         let requested_flush_root = 5; | ||||||
|  |         // Test that if there are > MAX_CACHE_SLOTS in the cache after flush, then more roots | ||||||
|  |         // will be flushed | ||||||
|  |         run_test_accounts_db_cache_clean_max_root( | ||||||
|  |             MAX_CACHE_SLOTS + requested_flush_root as usize + 2, | ||||||
|  |             requested_flush_root, | ||||||
|  |             Some(requested_flush_root - 1), | ||||||
|  |         ); | ||||||
|  |         run_test_accounts_db_cache_clean_max_root( | ||||||
|  |             MAX_CACHE_SLOTS + requested_flush_root as usize + 2, | ||||||
|  |             requested_flush_root, | ||||||
|  |             Some(requested_flush_root + 1), | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn run_flush_rooted_accounts_cache(should_clean: bool) { |     fn run_flush_rooted_accounts_cache(should_clean: bool) { | ||||||
|         let num_slots = 10; |         let num_slots = 10; | ||||||
|         let (accounts_db, keys, slots) = setup_accounts_db_cache_clean(num_slots); |         let (accounts_db, keys, slots, _) = setup_accounts_db_cache_clean(num_slots, None); | ||||||
|         let mut cleaned_bytes = 0; |         let mut cleaned_bytes = 0; | ||||||
|         let mut cleaned_accounts = 0; |         let mut cleaned_accounts = 0; | ||||||
|         let should_clean_tracker = if should_clean { |         let should_clean_tracker = if should_clean { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user