Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix finalize_dead_slot_removal() of cached slots decrementing refcount #15534

Merged
merged 2 commits into from
Feb 27, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 74 additions & 4 deletions runtime/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3946,17 +3946,19 @@ impl AccountsDb {
&'a self,
dead_slots_iter: impl Iterator<Item = &'a Slot> + Clone,
purged_slot_pubkeys: HashSet<(Slot, Pubkey)>,
mut purged_account_slots: Option<&mut AccountSlots>,
// If the slot being are not cached, return
is_cached: Option<&mut AccountSlots>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits: keep original var name? This paused me a whil because the type isn't bool.

) {
for (slot, pubkey) in purged_slot_pubkeys {
if let Some(ref mut purged_account_slots) = purged_account_slots {
if let Some(purged_account_slots) = is_cached {
for (slot, pubkey) in purged_slot_pubkeys {
purged_account_slots.entry(pubkey).or_default().insert(slot);
self.accounts_index.unref_from_storage(&pubkey);
}
self.accounts_index.unref_from_storage(&pubkey);
}

let mut accounts_index_root_stats = AccountsIndexRootsStats::default();
for slot in dead_slots_iter.clone() {
info!("finalize_dead_slot_removal slot {}", slot);
if let Some(latest) = self.accounts_index.clean_dead_slot(*slot) {
accounts_index_root_stats = latest;
}
Expand Down Expand Up @@ -8218,6 +8220,74 @@ pub mod tests {
.is_none());
}

#[test]
fn test_flush_cache_dont_clean_zero_lamport_account() {
let caching_enabled = true;
let db = Arc::new(AccountsDb::new_with_config(
Vec::new(),
&ClusterType::Development,
HashSet::new(),
caching_enabled,
));

let zero_lamport_account_key = Pubkey::new_unique();
let other_account_key = Pubkey::new_unique();

let original_lamports = 1;
let slot0_account = Account::new(original_lamports, 1, &Account::default().owner);
let zero_lamport_account = Account::new(0, 0, &Account::default().owner);

// Store into slot 0, and then flush the slot to storage
db.store_cached(0, &[(&zero_lamport_account_key, &slot0_account)]);
// Second key keeps other lamport account entry for slot 0 alive,
// preventing clean of the zero_lamport_account in slot 1.
db.store_cached(0, &[(&other_account_key, &slot0_account)]);
db.add_root(0);
db.flush_accounts_cache(true, None);
assert!(!db.storage.get_slot_storage_entries(0).unwrap().is_empty());

// Store into slot 1, a dummy slot that will be dead and purged before flush
db.store_cached(1, &[(&zero_lamport_account_key, &zero_lamport_account)]);

// Store into slot 2, which makes all updates from slot 1 outdated.
// This means slot 1 is a dead slot. Later, slot 1 will be cleaned/purged
// before it even reaches storage, but this purge of slot 1should not affect
// the refcount of `zero_lamport_account_key` because cached keys do not bump
// the refcount in the index. This means clean should *not* remove
// `zero_lamport_account_key` from slot 2
db.store_cached(2, &[(&zero_lamport_account_key, &zero_lamport_account)]);
db.add_root(1);
db.add_root(2);

// Flush, then clean. Should not need another root to initiate the cleaning
// because `accounts_index.uncleaned_roots` should be correct
db.flush_accounts_cache(true, None);
db.clean_accounts(None);

// The `zero_lamport_account_key` is still alive in slot 1, so refcount for the
// pubkey should be 2
assert_eq!(
db.accounts_index
.ref_count_from_storage(&zero_lamport_account_key),
2
);
assert_eq!(
db.accounts_index.ref_count_from_storage(&other_account_key),
1
);

// The zero-lamport account in slot 2 should not be purged yet, because the
// entry in slot 1 is blocking cleanup of the zero-lamport account.
let max_root = None;
assert_eq!(
db.do_load(&Ancestors::default(), &zero_lamport_account_key, max_root,)
.unwrap()
.0
.lamports,
0
);
}

struct ScanTracker {
t_scan: JoinHandle<()>,
exit: Arc<AtomicBool>,
Expand Down