From 1b30de4f320a9c73b77de6aa2cc57bab43d2867f Mon Sep 17 00:00:00 2001 From: Pankaj Garg Date: Sat, 3 Jun 2023 00:38:11 -0700 Subject: [PATCH] Retain runtime environment config for unloaded programs (#31953) * Retain runtime environment config for unloaded programs --- program-runtime/src/loaded_programs.rs | 139 +++++++++++++------------ programs/bpf_loader/src/lib.rs | 6 +- 2 files changed, 79 insertions(+), 66 deletions(-) diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index fe00e00b36..ead152762d 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -65,12 +65,12 @@ pub enum LoadedProgramType { Closed, DelayVisibility, /// Successfully verified but not currently compiled, used to track usage statistics when a compiled program is evicted from memory. - Unloaded, + Unloaded(Arc>>), LegacyV0(Executable>), LegacyV1(Executable>), Typed(Executable>), #[cfg(test)] - TestLoaded, + TestLoaded(Arc>>), Builtin(BuiltinProgram>), } @@ -82,12 +82,12 @@ impl Debug for LoadedProgramType { } LoadedProgramType::Closed => write!(f, "LoadedProgramType::Closed"), LoadedProgramType::DelayVisibility => write!(f, "LoadedProgramType::DelayVisibility"), - LoadedProgramType::Unloaded => write!(f, "LoadedProgramType::Unloaded"), + LoadedProgramType::Unloaded(_) => write!(f, "LoadedProgramType::Unloaded"), LoadedProgramType::LegacyV0(_) => write!(f, "LoadedProgramType::LegacyV0"), LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"), LoadedProgramType::Typed(_) => write!(f, "LoadedProgramType::Typed"), #[cfg(test)] - LoadedProgramType::TestLoaded => write!(f, "LoadedProgramType::TestLoaded"), + LoadedProgramType::TestLoaded(_) => write!(f, "LoadedProgramType::TestLoaded"), LoadedProgramType::Builtin(_) => write!(f, "LoadedProgramType::Builtin"), } } @@ -272,16 +272,24 @@ impl LoadedProgram { }) } - pub fn to_unloaded(&self) -> Self { - Self { - program: LoadedProgramType::Unloaded, + pub fn to_unloaded(&self) -> Option { + let env = match &self.program { + LoadedProgramType::LegacyV0(program) + | LoadedProgramType::LegacyV1(program) + | LoadedProgramType::Typed(program) => program.get_loader().clone(), + #[cfg(test)] + LoadedProgramType::TestLoaded(env) => env.clone(), + _ => return None, + }; + Some(Self { + program: LoadedProgramType::Unloaded(env), account_size: self.account_size, deployment_slot: self.deployment_slot, effective_slot: self.effective_slot, maybe_expiration_slot: self.maybe_expiration_slot, tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)), ix_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)), - } + }) } /// Creates a new built-in program @@ -330,17 +338,6 @@ impl LoadedProgram { ) } - fn is_loaded(&self) -> bool { - match self.program { - LoadedProgramType::LegacyV0(_) - | LoadedProgramType::LegacyV1(_) - | LoadedProgramType::Typed(_) => true, - #[cfg(test)] - LoadedProgramType::TestLoaded => true, - _ => false, - } - } - fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool { !matches!(self.program, LoadedProgramType::Builtin(_)) && self.effective_slot.saturating_sub(self.deployment_slot) @@ -447,7 +444,7 @@ impl LoadedPrograms { if existing.deployment_slot == entry.deployment_slot && existing.effective_slot == entry.effective_slot { - if matches!(existing.program, LoadedProgramType::Unloaded) { + if matches!(existing.program, LoadedProgramType::Unloaded(_)) { // The unloaded program is getting reloaded // Copy over the usage counter to the new entry let mut usage_count = existing.tx_usage_counter.load(Ordering::Relaxed); @@ -552,7 +549,7 @@ impl LoadedPrograms { Self::matches_loaded_program_criteria(entry, match_criteria) // If the program was unloaded. Consider it as unusable, so it can be reloaded. - && !matches!(entry.program, LoadedProgramType::Unloaded) + && !matches!(entry.program, LoadedProgramType::Unloaded(_)) } /// Extracts a subset of the programs relevant to a transaction batch @@ -636,8 +633,8 @@ impl LoadedPrograms { | LoadedProgramType::LegacyV1(_) | LoadedProgramType::Typed(_) => Some((*id, program.clone())), #[cfg(test)] - LoadedProgramType::TestLoaded => Some((*id, program.clone())), - LoadedProgramType::Unloaded + LoadedProgramType::TestLoaded(_) => Some((*id, program.clone())), + LoadedProgramType::Unloaded(_) | LoadedProgramType::FailedVerification | LoadedProgramType::Closed | LoadedProgramType::DelayVisibility @@ -682,8 +679,8 @@ impl LoadedPrograms { fn unload_program(&mut self, id: &Pubkey) { if let Some(entries) = self.entries.get_mut(id) { entries.iter_mut().for_each(|entry| { - if entry.is_loaded() { - *entry = Arc::new(entry.to_unloaded()); + if let Some(unloaded) = entry.to_unloaded() { + *entry = Arc::new(unloaded); self.stats .evictions .entry(*id) @@ -706,15 +703,17 @@ impl LoadedPrograms { for (id, program) in remove { if let Some(entries) = self.entries.get_mut(id) { if let Some(candidate) = entries.iter_mut().find(|entry| entry == &program) { - if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 { - self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed); + if let Some(unloaded) = candidate.to_unloaded() { + if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 { + self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed); + } + self.stats + .evictions + .entry(*id) + .and_modify(|c| saturating_add_assign!(*c, 1)) + .or_insert(1); + *candidate = Arc::new(unloaded); } - self.stats - .evictions - .entry(*id) - .and_modify(|c| saturating_add_assign!(*c, 1)) - .or_insert(1); - *candidate = Arc::new(candidate.to_unloaded()); } } } @@ -756,7 +755,7 @@ mod tests { LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET, }, percentage::Percentage, - solana_rbpf::vm::BuiltinProgram, + solana_rbpf::vm::{BuiltinProgram, Config}, solana_sdk::{clock::Slot, pubkey::Pubkey}, std::{ ops::ControlFlow, @@ -793,8 +792,11 @@ mod tests { key: Pubkey, slot: Slot, ) -> Arc { - let unloaded = - Arc::new(new_test_loaded_program(slot, slot.saturating_add(1)).to_unloaded()); + let unloaded = Arc::new( + new_test_loaded_program(slot, slot.saturating_add(1)) + .to_unloaded() + .expect("Failed to unload the program"), + ); cache.replenish(key, unloaded).1 } @@ -918,10 +920,10 @@ mod tests { programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count); let num_loaded = num_matching_entries(&cache, |program_type| { - matches!(program_type, LoadedProgramType::TestLoaded) + matches!(program_type, LoadedProgramType::TestLoaded(_)) }); let num_unloaded = num_matching_entries(&cache, |program_type| { - matches!(program_type, LoadedProgramType::Unloaded) + matches!(program_type, LoadedProgramType::Unloaded(_)) }); let num_tombstones = num_matching_entries(&cache, |program_type| { matches!( @@ -951,7 +953,7 @@ mod tests { .iter() .flat_map(|(id, cached_programs)| { cached_programs.iter().filter_map(|program| { - matches!(program.program, LoadedProgramType::Unloaded) + matches!(program.program, LoadedProgramType::Unloaded(_)) .then_some((*id, program.tx_usage_counter.load(Ordering::Relaxed))) }) }) @@ -963,10 +965,10 @@ mod tests { } let num_loaded = num_matching_entries(&cache, |program_type| { - matches!(program_type, LoadedProgramType::TestLoaded) + matches!(program_type, LoadedProgramType::TestLoaded(_)) }); let num_unloaded = num_matching_entries(&cache, |program_type| { - matches!(program_type, LoadedProgramType::Unloaded) + matches!(program_type, LoadedProgramType::Unloaded(_)) }); let num_tombstones = num_matching_entries(&cache, |program_type| { matches!( @@ -999,13 +1001,13 @@ mod tests { cache.sort_and_unload(Percentage::from(2)); let num_unloaded = num_matching_entries(&cache, |program_type| { - matches!(program_type, LoadedProgramType::Unloaded) + matches!(program_type, LoadedProgramType::Unloaded(_)) }); assert_eq!(num_unloaded, 1); cache.entries.values().for_each(|programs| { programs.iter().for_each(|program| { - if matches!(program.program, LoadedProgramType::Unloaded) { + if matches!(program.program, LoadedProgramType::Unloaded(_)) { // Test that the usage counter is retained for the unloaded program assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10); assert_eq!(program.deployment_slot, 0); @@ -1023,7 +1025,7 @@ mod tests { cache.entries.values().for_each(|programs| { programs.iter().for_each(|program| { - if matches!(program.program, LoadedProgramType::Unloaded) + if matches!(program.program, LoadedProgramType::Unloaded(_)) && program.deployment_slot == 0 && program.effective_slot == 2 { @@ -1259,17 +1261,33 @@ mod tests { fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc { new_test_loaded_program_with_usage(deployment_slot, effective_slot, AtomicU64::default()) } + fn new_test_loaded_program_with_usage( deployment_slot: Slot, effective_slot: Slot, usage_counter: AtomicU64, ) -> Arc { + new_test_loaded_program_with_usage_and_expiry( + deployment_slot, + effective_slot, + usage_counter, + None, + ) + } + + fn new_test_loaded_program_with_usage_and_expiry( + deployment_slot: Slot, + effective_slot: Slot, + usage_counter: AtomicU64, + expiry: Option, + ) -> Arc { + let env = Arc::new(BuiltinProgram::new_loader(Config::default())); Arc::new(LoadedProgram { - program: LoadedProgramType::TestLoaded, + program: LoadedProgramType::TestLoaded(env), account_size: 0, deployment_slot, effective_slot, - maybe_expiration_slot: None, + maybe_expiration_slot: expiry, tx_usage_counter: usage_counter, ix_usage_counter: AtomicU64::default(), }) @@ -1852,15 +1870,11 @@ mod tests { #[test] fn test_usable_entries_for_slot() { - let unloaded_entry = Arc::new(LoadedProgram { - program: LoadedProgramType::Unloaded, - account_size: 0, - deployment_slot: 0, - effective_slot: 0, - maybe_expiration_slot: None, - tx_usage_counter: AtomicU64::default(), - ix_usage_counter: AtomicU64::default(), - }); + let unloaded_entry = Arc::new( + new_test_loaded_program(0, 0) + .to_unloaded() + .expect("Failed to unload the program"), + ); assert!(!LoadedPrograms::is_entry_usable( &unloaded_entry, 0, @@ -1949,15 +1963,12 @@ mod tests { &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1) )); - let program = Arc::new(LoadedProgram { - program: LoadedProgramType::TestLoaded, - account_size: 0, - deployment_slot: 0, - effective_slot: 1, - maybe_expiration_slot: Some(2), - tx_usage_counter: AtomicU64::default(), - ix_usage_counter: AtomicU64::default(), - }); + let program = Arc::new(new_test_loaded_program_with_usage_and_expiry( + 0, + 1, + AtomicU64::default(), + Some(2), + )); assert!(LoadedPrograms::is_entry_usable( &program, diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 1cea36f5c4..69fdeda3b6 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -4128,8 +4128,9 @@ mod tests { let transaction_accounts = vec![]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let program_id = Pubkey::new_unique(); + let env = Arc::new(BuiltinProgram::new_loader(Config::default())); let program = LoadedProgram { - program: LoadedProgramType::Unloaded, + program: LoadedProgramType::Unloaded(env), account_size: 0, deployment_slot: 0, effective_slot: 0, @@ -4167,8 +4168,9 @@ mod tests { let transaction_accounts = vec![]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let program_id = Pubkey::new_unique(); + let env = Arc::new(BuiltinProgram::new_loader(Config::default())); let program = LoadedProgram { - program: LoadedProgramType::Unloaded, + program: LoadedProgramType::Unloaded(env), account_size: 0, deployment_slot: 0, effective_slot: 0,