From 21780fd76aced07cdbdf3cb3d4b0a445314592b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Sun, 19 Nov 2023 18:43:26 +0100 Subject: [PATCH 1/3] Puts ExtractedPrograms into Arc>. --- program-runtime/src/loaded_programs.rs | 384 ++++++++++--------------- runtime/src/bank.rs | 8 +- 2 files changed, 159 insertions(+), 233 deletions(-) diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index 6452618b9e44a1..6efe9df5415608 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -25,7 +25,7 @@ use { fmt::{Debug, Formatter}, sync::{ atomic::{AtomicU64, Ordering}, - Arc, RwLock, + Arc, Mutex, RwLock, }, }, }; @@ -795,12 +795,20 @@ impl LoadedPrograms { &self, working_slot: &S, keys: impl Iterator, - ) -> ExtractedPrograms { + ) -> Arc> { let environments = self.get_environments_for_epoch(working_slot.current_epoch()); - let mut missing = Vec::new(); - let mut unloaded = Vec::new(); let current_slot = working_slot.current_slot(); - let found = keys + let extracted = Arc::new(Mutex::new(ExtractedPrograms { + loaded: LoadedProgramsForTxBatch { + entries: HashMap::new(), + slot: current_slot, + environments: environments.clone(), + }, + missing: Vec::new(), + unloaded: Vec::new(), + })); + let mut extracting = extracted.lock().unwrap(); + extracting.loaded.entries = keys .filter_map(|(key, (match_criteria, count))| { if let Some(second_level) = self.entries.get(&key) { for entry in second_level.iter().rev() { @@ -825,17 +833,17 @@ impl LoadedPrograms { { if current_slot >= entry.effective_slot { if !Self::is_entry_usable(entry, current_slot, &match_criteria) { - missing.push((key, count)); + extracting.missing.push((key, count)); return None; } if !Self::matches_environment(entry, environments) { - missing.push((key, count)); + extracting.missing.push((key, count)); return None; } if let LoadedProgramType::Unloaded(_environment) = &entry.program { - unloaded.push((key, count)); + extracting.unloaded.push((key, count)); return None; } @@ -859,26 +867,18 @@ impl LoadedPrograms { } } } - missing.push((key, count)); + extracting.missing.push((key, count)); None }) .collect::>>(); - self.stats .misses - .fetch_add(missing.len() as u64, Ordering::Relaxed); + .fetch_add(extracting.missing.len() as u64, Ordering::Relaxed); self.stats .hits - .fetch_add(found.len() as u64, Ordering::Relaxed); - ExtractedPrograms { - loaded: LoadedProgramsForTxBatch { - entries: found, - slot: current_slot, - environments: environments.clone(), - }, - missing, - unloaded, - } + .fetch_add(extracting.loaded.entries.len() as u64, Ordering::Relaxed); + drop(extracting); + extracted } pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) { @@ -1010,7 +1010,7 @@ mod tests { use { crate::loaded_programs::{ BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, - LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment, + LoadedProgramType, LoadedPrograms, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET, }, assert_matches::assert_matches, @@ -1024,7 +1024,7 @@ mod tests { ops::ControlFlow, sync::{ atomic::{AtomicU64, Ordering}, - Arc, RwLock, + Arc, Mutex, RwLock, }, }, }; @@ -1630,18 +1630,34 @@ mod tests { } fn match_slot( - table: &LoadedProgramsForTxBatch, + extracted: &Arc>, program: &Pubkey, deployment_slot: Slot, working_slot: Slot, ) -> bool { - assert_eq!(table.slot, working_slot); - table + let extracted = extracted.lock().unwrap(); + assert_eq!(extracted.loaded.slot, working_slot); + extracted + .loaded .find(program) .map(|entry| entry.deployment_slot == deployment_slot) .unwrap_or(false) } + fn match_missing( + extracted: &Arc>, + program: &Pubkey, + reload: bool, + ) -> bool { + let extracted = extracted.lock().unwrap(); + let list = if reload { + &extracted.unloaded + } else { + &extracted.missing + }; + list.iter().any(|(key, _count)| key == program) + } + #[test] fn test_fork_extract_and_prune() { let mut cache = new_mock_cache::(); @@ -1720,11 +1736,7 @@ mod tests { // 23 // Testing fork 0 - 10 - 12 - 22 with current slot at 22 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(22), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1735,19 +1747,14 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 20, 22)); - assert!(match_slot(&found, &program4, 0, 22)); + assert!(match_slot(&extracted, &program1, 20, 22)); + assert!(match_slot(&extracted, &program4, 0, 22)); - assert!(missing.contains(&(program2, 2))); - assert!(missing.contains(&(program3, 3))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program2, false)); + assert!(match_missing(&extracted, &program3, false)); - // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 15 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16 + let extracted = cache.extract( &TestWorkingSlot(15), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1758,24 +1765,24 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 15)); - assert!(match_slot(&found, &program2, 11, 15)); + assert!(match_slot(&extracted, &program1, 0, 15)); + assert!(match_slot(&extracted, &program2, 11, 15)); // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16. // A delay visibility tombstone should be returned here. - let tombstone = found.find(&program4).expect("Failed to find the tombstone"); + let tombstone = extracted + .lock() + .unwrap() + .loaded + .find(&program4) + .expect("Failed to find the tombstone"); assert_matches!(tombstone.program, LoadedProgramType::DelayVisibility); assert_eq!(tombstone.deployment_slot, 15); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4). - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(18), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1786,21 +1793,16 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 18)); - assert!(match_slot(&found, &program2, 11, 18)); + assert!(match_slot(&extracted, &program1, 0, 18)); + assert!(match_slot(&extracted, &program2, 11, 18)); // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18. - assert!(match_slot(&found, &program4, 15, 18)); + assert!(match_slot(&extracted, &program4, 15, 18)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4). - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(23), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1811,21 +1813,16 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 23)); - assert!(match_slot(&found, &program2, 11, 23)); + assert!(match_slot(&extracted, &program1, 0, 23)); + assert!(match_slot(&extracted, &program2, 11, 23)); // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23. - assert!(match_slot(&found, &program4, 15, 23)); + assert!(match_slot(&extracted, &program4, 15, 23)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(11), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1836,15 +1833,19 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 11)); + assert!(match_slot(&extracted, &program1, 0, 11)); // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone. - let tombstone = found.find(&program2).expect("Failed to find the tombstone"); + let tombstone = extracted + .lock() + .unwrap() + .loaded + .find(&program2) + .expect("Failed to find the tombstone"); assert_matches!(tombstone.program, LoadedProgramType::DelayVisibility); assert_eq!(tombstone.deployment_slot, 11); - assert!(match_slot(&found, &program4, 5, 11)); + assert!(match_slot(&extracted, &program4, 5, 11)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // The following is a special case, where there's an expiration slot let test_program = Arc::new(LoadedProgram { @@ -1859,11 +1860,7 @@ mod tests { assert!(!cache.replenish(program4, test_program).0); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(19), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1874,21 +1871,16 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 19)); - assert!(match_slot(&found, &program2, 11, 19)); + assert!(match_slot(&extracted, &program1, 0, 19)); + assert!(match_slot(&extracted, &program2, 11, 19)); // Program4 deployed at slot 19 should not be expired yet - assert!(match_slot(&found, &program4, 19, 19)); + assert!(match_slot(&extracted, &program4, 19, 19)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 21 // This would cause program4 deployed at slot 19 to be expired. - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(21), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1899,12 +1891,11 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 21)); - assert!(match_slot(&found, &program2, 11, 21)); + assert!(match_slot(&extracted, &program1, 0, 21)); + assert!(match_slot(&extracted, &program2, 11, 21)); - assert!(missing.contains(&(program3, 1))); - assert!(missing.contains(&(program4, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); + assert!(match_missing(&extracted, &program4, false)); // Remove the expired entry to let the rest of the test continue if let Some(programs) = cache.entries.get_mut(&program4) { @@ -1928,12 +1919,8 @@ mod tests { // | // 23 - // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 21 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22 + let extracted = cache.extract( &TestWorkingSlot(21), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1945,19 +1932,14 @@ mod tests { ); // Since the fork was pruned, we should not find the entry deployed at slot 20. - assert!(match_slot(&found, &program1, 0, 21)); - assert!(match_slot(&found, &program2, 11, 21)); - assert!(match_slot(&found, &program4, 15, 21)); + assert!(match_slot(&extracted, &program1, 0, 21)); + assert!(match_slot(&extracted, &program2, 11, 21)); + assert!(match_slot(&extracted, &program4, 15, 21)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(27), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1968,11 +1950,10 @@ mod tests { .into_iter(), ); - assert!(unloaded.is_empty()); - assert!(match_slot(&found, &program1, 0, 27)); - assert!(match_slot(&found, &program2, 11, 27)); - assert!(match_slot(&found, &program3, 25, 27)); - assert!(match_slot(&found, &program4, 5, 27)); + assert!(match_slot(&extracted, &program1, 0, 27)); + assert!(match_slot(&extracted, &program2, 11, 27)); + assert!(match_slot(&extracted, &program3, 25, 27)); + assert!(match_slot(&extracted, &program4, 5, 27)); cache.prune(15, 0); @@ -1992,11 +1973,7 @@ mod tests { // 23 // Testing fork 16, 19, 23, with root at 15, current slot at 23 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(23), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2007,13 +1984,12 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 23)); - assert!(match_slot(&found, &program2, 11, 23)); - assert!(match_slot(&found, &program4, 15, 23)); + assert!(match_slot(&extracted, &program1, 0, 23)); + assert!(match_slot(&extracted, &program2, 11, 23)); + assert!(match_slot(&extracted, &program4, 15, 23)); // program3 was deployed on slot 25, which has been pruned - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); } #[test] @@ -2055,11 +2031,7 @@ mod tests { assert!(!cache.replenish(program3, new_test_loaded_program(25, 26)).0); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(12), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2069,18 +2041,13 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 12)); - assert!(match_slot(&found, &program2, 11, 12)); + assert!(match_slot(&extracted, &program1, 0, 12)); + assert!(match_slot(&extracted, &program2, 11, 12)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Test the same fork, but request the program modified at a later slot than what's in the cache. - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(12), vec![ ( @@ -2096,11 +2063,10 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program2, 11, 12)); + assert!(match_slot(&extracted, &program2, 11, 12)); - assert!(missing.contains(&(program1, 1))); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program1, false)); + assert!(match_missing(&extracted, &program3, false)); } #[test] @@ -2159,11 +2125,7 @@ mod tests { ); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(19), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2173,18 +2135,13 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 19)); - assert!(match_slot(&found, &program2, 11, 19)); + assert!(match_slot(&extracted, &program1, 0, 19)); + assert!(match_slot(&extracted, &program2, 11, 19)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(27), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2194,18 +2151,13 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 27)); - assert!(match_slot(&found, &program2, 11, 27)); + assert!(match_slot(&extracted, &program1, 0, 27)); + assert!(match_slot(&extracted, &program2, 11, 27)); - assert!(unloaded.contains(&(program3, 1))); - assert!(missing.is_empty()); + assert!(match_missing(&extracted, &program3, true)); // Testing fork 0 - 10 - 20 - 22 with current slot at 22 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(22), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2215,10 +2167,10 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 20, 22)); + assert!(match_slot(&extracted, &program1, 20, 22)); - assert!(missing.contains(&(program2, 1))); - assert!(unloaded.contains(&(program3, 1))); + assert!(match_missing(&extracted, &program2, false)); + assert!(match_missing(&extracted, &program3, true)); } #[test] @@ -2271,11 +2223,7 @@ mod tests { assert!(!cache.replenish(program1, test_program).0); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(12), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2286,19 +2234,14 @@ mod tests { ); // Program1 deployed at slot 11 should not be expired yet - assert!(match_slot(&found, &program1, 11, 12)); - assert!(match_slot(&found, &program2, 11, 12)); + assert!(match_slot(&extracted, &program1, 11, 12)); + assert!(match_slot(&extracted, &program2, 11, 12)); - assert!(missing.contains(&(program3, 1))); - assert!(unloaded.is_empty()); + assert!(match_missing(&extracted, &program3, false)); // Testing fork 0 - 5 - 11 - 12 - 15 - 16 - 19 - 21 - 23 with current slot at 15 // This would cause program4 deployed at slot 15 to be expired. - let ExtractedPrograms { - loaded: found, - missing, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(15), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2307,12 +2250,11 @@ mod tests { ] .into_iter(), ); - assert!(unloaded.is_empty()); - assert!(match_slot(&found, &program2, 11, 15)); + assert!(match_slot(&extracted, &program2, 11, 15)); - assert!(missing.contains(&(program1, 1))); - assert!(missing.contains(&(program3, 1))); + assert!(match_missing(&extracted, &program1, false)); + assert!(match_missing(&extracted, &program3, false)); // Test that the program still exists in the cache, even though it is expired. assert_eq!( @@ -2366,19 +2308,17 @@ mod tests { cache.prune(10, 0); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(20), vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(), ); - assert!(unloaded.is_empty()); // The cache should have the program deployed at slot 0 assert_eq!( - found + extracted + .lock() + .unwrap() + .loaded .entries .get(&program1) .expect("Did not find the program") @@ -2414,11 +2354,7 @@ mod tests { let program2 = Pubkey::new_unique(); assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded: _, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(20), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2427,14 +2363,10 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 20)); - assert!(match_slot(&found, &program2, 10, 20)); + assert!(match_slot(&extracted, &program1, 0, 20)); + assert!(match_slot(&extracted, &program2, 10, 20)); - let ExtractedPrograms { - loaded: found, - missing, - unloaded: _, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(6), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2443,18 +2375,14 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 5, 6)); - assert!(missing.contains(&(program2, 1))); + assert!(match_slot(&extracted, &program1, 5, 6)); + assert!(match_missing(&extracted, &program2, false)); // Pruning slot 5 will remove program1 entry deployed at slot 5. // On fork chaining from slot 5, the entry deployed at slot 0 will become visible. cache.prune_by_deployment_slot(5); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded: _, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(20), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2463,14 +2391,10 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 20)); - assert!(match_slot(&found, &program2, 10, 20)); + assert!(match_slot(&extracted, &program1, 0, 20)); + assert!(match_slot(&extracted, &program2, 10, 20)); - let ExtractedPrograms { - loaded: found, - missing, - unloaded: _, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(6), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2479,18 +2403,14 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 6)); - assert!(missing.contains(&(program2, 1))); + assert!(match_slot(&extracted, &program1, 0, 6)); + assert!(match_missing(&extracted, &program2, false)); // Pruning slot 10 will remove program2 entry deployed at slot 10. // As there is no other entry for program2, extract() will return it as missing. cache.prune_by_deployment_slot(10); - let ExtractedPrograms { - loaded: found, - missing: _, - unloaded: _, - } = cache.extract( + let extracted = cache.extract( &TestWorkingSlot(20), vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2499,8 +2419,8 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0, 20)); - assert!(missing.contains(&(program2, 1))); + assert!(match_slot(&extracted, &program1, 0, 20)); + assert!(match_missing(&extracted, &program2, false)); } #[test] diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index bc4344fdc42301..18e65ab1994c73 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5075,7 +5075,13 @@ impl Bank { } = { // Lock the global cache to figure out which programs need to be loaded let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); - loaded_programs_cache.extract(self, programs_and_slots.into_iter()) + Mutex::into_inner( + Arc::into_inner( + loaded_programs_cache.extract(self, programs_and_slots.into_iter()), + ) + .unwrap(), + ) + .unwrap() }; // Load missing programs while global cache is unlocked From 5edd283a001e7c2b208f721b4022d0a09791e16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Sat, 21 Oct 2023 18:47:30 +0200 Subject: [PATCH 2/3] Merges ExtractedPrograms::missing and ExtractedPrograms::unloaded. --- program-runtime/src/loaded_programs.rs | 34 +++++++++++--------------- runtime/src/bank.rs | 21 ++-------------- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index 6efe9df5415608..607d2a7bb80268 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -511,8 +511,7 @@ pub struct LoadedProgramsForTxBatch { pub struct ExtractedPrograms { pub loaded: LoadedProgramsForTxBatch, - pub missing: Vec<(Pubkey, u64)>, - pub unloaded: Vec<(Pubkey, u64)>, + pub missing: HashMap, } impl LoadedProgramsForTxBatch { @@ -804,8 +803,7 @@ impl LoadedPrograms { slot: current_slot, environments: environments.clone(), }, - missing: Vec::new(), - unloaded: Vec::new(), + missing: HashMap::new(), })); let mut extracting = extracted.lock().unwrap(); extracting.loaded.entries = keys @@ -832,18 +830,15 @@ impl LoadedPrograms { || is_ancestor { if current_slot >= entry.effective_slot { - if !Self::is_entry_usable(entry, current_slot, &match_criteria) { - extracting.missing.push((key, count)); - return None; - } - - if !Self::matches_environment(entry, environments) { - extracting.missing.push((key, count)); + if !Self::is_entry_usable(entry, current_slot, &match_criteria) + || !Self::matches_environment(entry, environments) + { + extracting.missing.insert(key, (count, false)); return None; } if let LoadedProgramType::Unloaded(_environment) = &entry.program { - extracting.unloaded.push((key, count)); + extracting.missing.insert(key, (count, true)); return None; } @@ -867,7 +862,7 @@ impl LoadedPrograms { } } } - extracting.missing.push((key, count)); + extracting.missing.insert(key, (count, false)); None }) .collect::>>(); @@ -1646,16 +1641,15 @@ mod tests { fn match_missing( extracted: &Arc>, - program: &Pubkey, + key: &Pubkey, reload: bool, ) -> bool { let extracted = extracted.lock().unwrap(); - let list = if reload { - &extracted.unloaded - } else { - &extracted.missing - }; - list.iter().any(|(key, _count)| key == program) + extracted + .missing + .get(key) + .filter(|(_count, reloading)| *reloading == reload) + .is_some() } #[test] diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 18e65ab1994c73..09da2293f513ba 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5071,7 +5071,6 @@ impl Bank { let ExtractedPrograms { loaded: mut loaded_programs_for_txs, missing, - unloaded, } = { // Lock the global cache to figure out which programs need to be loaded let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); @@ -5087,18 +5086,8 @@ impl Bank { // Load missing programs while global cache is unlocked let missing_programs: Vec<(Pubkey, Arc)> = missing .iter() - .map(|(key, count)| { - let program = self.load_program(key, false, None); - program.tx_usage_counter.store(*count, Ordering::Relaxed); - (*key, program) - }) - .collect(); - - // Reload unloaded programs while global cache is unlocked - let unloaded_programs: Vec<(Pubkey, Arc)> = unloaded - .iter() - .map(|(key, count)| { - let program = self.load_program(key, true, None); + .map(|(key, (count, reloading))| { + let program = self.load_program(key, *reloading, None); program.tx_usage_counter.store(*count, Ordering::Relaxed); (*key, program) }) @@ -5111,12 +5100,6 @@ impl Bank { // Use the returned entry as that might have been deduplicated globally loaded_programs_for_txs.replenish(key, entry); } - for (key, program) in unloaded_programs { - let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program); - // Use the returned entry as that might have been deduplicated globally - loaded_programs_for_txs.replenish(key, entry); - } - loaded_programs_for_txs } From f35494128e682a3db068303dcd1dbc499aacf86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Wed, 22 Nov 2023 16:05:12 +0100 Subject: [PATCH 3/3] Unifies missing entry insertion in LoadedPrograms::extract(). --- program-runtime/src/loaded_programs.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index 607d2a7bb80268..c210b8d0e9b314 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -808,6 +808,7 @@ impl LoadedPrograms { let mut extracting = extracted.lock().unwrap(); extracting.loaded.entries = keys .filter_map(|(key, (match_criteria, count))| { + let mut reloading = false; if let Some(second_level) = self.entries.get(&key) { for entry in second_level.iter().rev() { let is_ancestor = if let Some(fork_graph) = &self.fork_graph { @@ -833,13 +834,12 @@ impl LoadedPrograms { if !Self::is_entry_usable(entry, current_slot, &match_criteria) || !Self::matches_environment(entry, environments) { - extracting.missing.insert(key, (count, false)); - return None; + break; } if let LoadedProgramType::Unloaded(_environment) = &entry.program { - extracting.missing.insert(key, (count, true)); - return None; + reloading = true; + break; } let mut usage_count = @@ -862,7 +862,7 @@ impl LoadedPrograms { } } } - extracting.missing.insert(key, (count, false)); + extracting.missing.insert(key, (count, reloading)); None }) .collect::>>();