8000 v1.17: Do not prune cache entry if the runtime environment is different (backport of #34100) by mergify[bot] · Pull Request #34163 · solana-labs/solana · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

v1.17: Do not prune cache entry if the runtime environment is different (backport of #34100) #34163

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Changes from all commits
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
103 changes: 97 additions & 6 deletions program-runtime/src/loaded_programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ impl LoadedProgramType {
LoadedProgramType::LegacyV0(program)
| LoadedProgramType::LegacyV1(program)
| LoadedProgramType::Typed(program) => Some(program.get_loader()),
LoadedProgramType::FailedVerification(env) | LoadedProgramType::Unloaded(env) => {
Some(env)
}
#[cfg(test)]
LoadedProgramType::TestLoaded(environment) => Some(environment),
_ => None,
Expand Down Expand Up @@ -669,19 +672,37 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
for second_level in self.entries.values_mut() {
// Remove entries un/re/deployed on orphan forks
let mut first_ancestor_found = false;
let mut first_ancestor_env = None;
*second_level = second_level
.iter()
.rev()
.filter(|entry| {
let relation = fork_graph.relationship(entry.deployment_slot, new_root_slot);
if entry.deployment_slot >= new_root_slot {
matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
} else if !first_ancestor_found
&& (matches!(relation, BlockRelation::Ancestor)
|| entry.deployment_slot <= self.latest_root_slot)
} else if matches!(relation, BlockRelation::Ancestor)
|| entry.deployment_slot <= self.latest_root_slot
{
first_ancestor_found = true;
first_ancestor_found
if !first_ancestor_found {
first_ancestor_found = true;
first_ancestor_env = entry.program.get_environment();
return true;
}
// Do not prune the entry if the runtime environment of the entry is different
// than the entry that was previously found (stored in first_ancestor_env).
// Different environment indicates that this entry might belong to an older
// epoch that had a different environment (e.g. different feature set).
// Once the root moves to the new/current epoch, the entry will get pruned.
// But, until then the entry might still be getting used by an older slot.
if let Some(entry_env) = entry.program.get_environment() {
if let Some(env) = first_ancestor_env {
if !Arc::ptr_eq(entry_env, env) {
return true;
}
}
}
self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
false
} else {
self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
false
Expand Down Expand Up @@ -980,7 +1001,7 @@ mod tests {
crate::loaded_programs::{
BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria,
LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment,
WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET,
ProgramRuntimeEnvironments, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET,
},
assert_matches::assert_matches,
percentage::Percentage,
Expand Down Expand Up @@ -1471,6 +1492,76 @@ mod tests {
assert!(cache.entries.is_empty());
}

#[test]
fn test_prune_different_env() {
let mut cache = new_mock_cache::<TestForkGraph>();

let fork_graph = Arc::new(RwLock::new(TestForkGraph {
relation: BlockRelation::Ancestor,
}));

cache.set_fork_graph(fork_graph);

let program1 = Pubkey::new_unique();
let loaded_program = new_test_loaded_program(10, 10);
let (existing, program) = cache.replenish(program1, loaded_program.clone());
assert!(!existing);
assert_eq!(program, loaded_program);

let new_env = Arc::new(BuiltinProgram::new_mock());
cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
program_runtime_v1: new_env.clone(),
program_runtime_v2: new_env.clone(),
});
let updated_program = Arc::new(LoadedProgram {
program: LoadedProgramType::TestLoaded(new_env.clone()),
account_size: 0,
deployment_slot: 20,
effective_slot: 20,
maybe_expiration_slot: None,
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
});
let (existing, program) = cache.replenish(program1, updated_program.clone());
assert!(!existing);
assert_eq!(program, updated_program);

// Test that there are 2 entries for the program
assert_eq!(
cache
.entries
.get(&program1)
.expect("failed to find the program")
.len(),
2
);

cache.prune(21, cache.latest_root_epoch);

// Test that prune didn't remove the entry, since environments are different.
assert_eq!(
cache
.entries
.get(&program1)
.expect("failed to find the program")
.len(),
2
);

cache.prune(22, cache.latest_root_epoch.saturating_add(1));

let entries = cache
.entries
.get(&program1)
.expect("failed to find the program");
// Test that prune removed 1 entry, since epoch changed
assert_eq!(entries.len(), 1);

let entry = entries.first().expect("Failed to get the program").clone();
// Test that the correct entry remains in the cache
assert_eq!(entry, updated_program);
}

#[derive(Default)]
struct TestForkGraphSpecific {
forks: Vec<Vec<Slot>>,
Expand Down
0