Refactor - `ExtractedPrograms` (#34205)
* Puts ExtractedPrograms into Arc<Mutex<>>. * Merges ExtractedPrograms::missing and ExtractedPrograms::unloaded. * Unifies missing entry insertion in LoadedPrograms::extract().
This commit is contained in:
parent
6d703edd2e
commit
4ee5078e5f
|
@ -25,7 +25,7 @@ use {
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
Arc, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -511,8 +511,7 @@ pub struct LoadedProgramsForTxBatch {
|
||||||
|
|
||||||
pub struct ExtractedPrograms {
|
pub struct ExtractedPrograms {
|
||||||
pub loaded: LoadedProgramsForTxBatch,
|
pub loaded: LoadedProgramsForTxBatch,
|
||||||
pub missing: Vec<(Pubkey, u64)>,
|
pub missing: HashMap<Pubkey, (u64, bool)>,
|
||||||
pub unloaded: Vec<(Pubkey, u64)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoadedProgramsForTxBatch {
|
impl LoadedProgramsForTxBatch {
|
||||||
|
@ -795,13 +794,21 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
|
||||||
&self,
|
&self,
|
||||||
working_slot: &S,
|
working_slot: &S,
|
||||||
keys: impl Iterator<Item = (Pubkey, (LoadedProgramMatchCriteria, u64))>,
|
keys: impl Iterator<Item = (Pubkey, (LoadedProgramMatchCriteria, u64))>,
|
||||||
) -> ExtractedPrograms {
|
) -> Arc<Mutex<ExtractedPrograms>> {
|
||||||
let environments = self.get_environments_for_epoch(working_slot.current_epoch());
|
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 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: HashMap::new(),
|
||||||
|
}));
|
||||||
|
let mut extracting = extracted.lock().unwrap();
|
||||||
|
extracting.loaded.entries = keys
|
||||||
.filter_map(|(key, (match_criteria, count))| {
|
.filter_map(|(key, (match_criteria, count))| {
|
||||||
|
let mut reloading = false;
|
||||||
if let Some(second_level) = self.entries.get(&key) {
|
if let Some(second_level) = self.entries.get(&key) {
|
||||||
for entry in second_level.iter().rev() {
|
for entry in second_level.iter().rev() {
|
||||||
let is_ancestor = if let Some(fork_graph) = &self.fork_graph {
|
let is_ancestor = if let Some(fork_graph) = &self.fork_graph {
|
||||||
|
@ -824,19 +831,15 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
|
||||||
|| is_ancestor
|
|| is_ancestor
|
||||||
{
|
{
|
||||||
if current_slot >= entry.effective_slot {
|
if current_slot >= entry.effective_slot {
|
||||||
if !Self::is_entry_usable(entry, current_slot, &match_criteria) {
|
if !Self::is_entry_usable(entry, current_slot, &match_criteria)
|
||||||
missing.push((key, count));
|
|| !Self::matches_environment(entry, environments)
|
||||||
return None;
|
{
|
||||||
}
|
break;
|
||||||
|
|
||||||
if !Self::matches_environment(entry, environments) {
|
|
||||||
missing.push((key, count));
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let LoadedProgramType::Unloaded(_environment) = &entry.program {
|
if let LoadedProgramType::Unloaded(_environment) = &entry.program {
|
||||||
unloaded.push((key, count));
|
reloading = true;
|
||||||
return None;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut usage_count =
|
let mut usage_count =
|
||||||
|
@ -859,26 +862,18 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
missing.push((key, count));
|
extracting.missing.insert(key, (count, reloading));
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.collect::<HashMap<Pubkey, Arc<LoadedProgram>>>();
|
.collect::<HashMap<Pubkey, Arc<LoadedProgram>>>();
|
||||||
|
|
||||||
self.stats
|
self.stats
|
||||||
.misses
|
.misses
|
||||||
.fetch_add(missing.len() as u64, Ordering::Relaxed);
|
.fetch_add(extracting.missing.len() as u64, Ordering::Relaxed);
|
||||||
self.stats
|
self.stats
|
||||||
.hits
|
.hits
|
||||||
.fetch_add(found.len() as u64, Ordering::Relaxed);
|
.fetch_add(extracting.loaded.entries.len() as u64, Ordering::Relaxed);
|
||||||
ExtractedPrograms {
|
drop(extracting);
|
||||||
loaded: LoadedProgramsForTxBatch {
|
extracted
|
||||||
entries: found,
|
|
||||||
slot: current_slot,
|
|
||||||
environments: environments.clone(),
|
|
||||||
},
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) {
|
pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) {
|
||||||
|
@ -1010,7 +1005,7 @@ mod tests {
|
||||||
use {
|
use {
|
||||||
crate::loaded_programs::{
|
crate::loaded_programs::{
|
||||||
BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria,
|
BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria,
|
||||||
LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment,
|
LoadedProgramType, LoadedPrograms, ProgramRuntimeEnvironment,
|
||||||
ProgramRuntimeEnvironments, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET,
|
ProgramRuntimeEnvironments, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET,
|
||||||
},
|
},
|
||||||
assert_matches::assert_matches,
|
assert_matches::assert_matches,
|
||||||
|
@ -1024,7 +1019,7 @@ mod tests {
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
Arc, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1630,18 +1625,33 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_slot(
|
fn match_slot(
|
||||||
table: &LoadedProgramsForTxBatch,
|
extracted: &Arc<Mutex<ExtractedPrograms>>,
|
||||||
program: &Pubkey,
|
program: &Pubkey,
|
||||||
deployment_slot: Slot,
|
deployment_slot: Slot,
|
||||||
working_slot: Slot,
|
working_slot: Slot,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
assert_eq!(table.slot, working_slot);
|
let extracted = extracted.lock().unwrap();
|
||||||
table
|
assert_eq!(extracted.loaded.slot, working_slot);
|
||||||
|
extracted
|
||||||
|
.loaded
|
||||||
.find(program)
|
.find(program)
|
||||||
.map(|entry| entry.deployment_slot == deployment_slot)
|
.map(|entry| entry.deployment_slot == deployment_slot)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn match_missing(
|
||||||
|
extracted: &Arc<Mutex<ExtractedPrograms>>,
|
||||||
|
key: &Pubkey,
|
||||||
|
reload: bool,
|
||||||
|
) -> bool {
|
||||||
|
let extracted = extracted.lock().unwrap();
|
||||||
|
extracted
|
||||||
|
.missing
|
||||||
|
.get(key)
|
||||||
|
.filter(|(_count, reloading)| *reloading == reload)
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fork_extract_and_prune() {
|
fn test_fork_extract_and_prune() {
|
||||||
let mut cache = new_mock_cache::<TestForkGraphSpecific>();
|
let mut cache = new_mock_cache::<TestForkGraphSpecific>();
|
||||||
|
@ -1720,11 +1730,7 @@ mod tests {
|
||||||
// 23
|
// 23
|
||||||
|
|
||||||
// Testing fork 0 - 10 - 12 - 22 with current slot at 22
|
// Testing fork 0 - 10 - 12 - 22 with current slot at 22
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(22),
|
&TestWorkingSlot(22),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1735,19 +1741,14 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 20, 22));
|
assert!(match_slot(&extracted, &program1, 20, 22));
|
||||||
assert!(match_slot(&found, &program4, 0, 22));
|
assert!(match_slot(&extracted, &program4, 0, 22));
|
||||||
|
|
||||||
assert!(missing.contains(&(program2, 2)));
|
assert!(match_missing(&extracted, &program2, false));
|
||||||
assert!(missing.contains(&(program3, 3)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 15
|
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(15),
|
&TestWorkingSlot(15),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1758,24 +1759,24 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 15));
|
assert!(match_slot(&extracted, &program1, 0, 15));
|
||||||
assert!(match_slot(&found, &program2, 11, 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.
|
// 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.
|
// 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_matches!(tombstone.program, LoadedProgramType::DelayVisibility);
|
||||||
assert_eq!(tombstone.deployment_slot, 15);
|
assert_eq!(tombstone.deployment_slot, 15);
|
||||||
|
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
|
// Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(18),
|
&TestWorkingSlot(18),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1786,21 +1787,16 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 18));
|
assert!(match_slot(&extracted, &program1, 0, 18));
|
||||||
assert!(match_slot(&found, &program2, 11, 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.
|
// 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!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
|
// Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(23),
|
&TestWorkingSlot(23),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1811,21 +1807,16 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 23));
|
assert!(match_slot(&extracted, &program1, 0, 23));
|
||||||
assert!(match_slot(&found, &program2, 11, 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.
|
// 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!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
|
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(11),
|
&TestWorkingSlot(11),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1836,15 +1827,19 @@ mod tests {
|
||||||
.into_iter(),
|
.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.
|
// 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_matches!(tombstone.program, LoadedProgramType::DelayVisibility);
|
||||||
assert_eq!(tombstone.deployment_slot, 11);
|
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!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// The following is a special case, where there's an expiration slot
|
// The following is a special case, where there's an expiration slot
|
||||||
let test_program = Arc::new(LoadedProgram {
|
let test_program = Arc::new(LoadedProgram {
|
||||||
|
@ -1859,11 +1854,7 @@ mod tests {
|
||||||
assert!(!cache.replenish(program4, test_program).0);
|
assert!(!cache.replenish(program4, test_program).0);
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(19),
|
&TestWorkingSlot(19),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1874,21 +1865,16 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 19));
|
assert!(match_slot(&extracted, &program1, 0, 19));
|
||||||
assert!(match_slot(&found, &program2, 11, 19));
|
assert!(match_slot(&extracted, &program2, 11, 19));
|
||||||
// Program4 deployed at slot 19 should not be expired yet
|
// 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!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 21
|
// 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.
|
// This would cause program4 deployed at slot 19 to be expired.
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(21),
|
&TestWorkingSlot(21),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1899,12 +1885,11 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 21));
|
assert!(match_slot(&extracted, &program1, 0, 21));
|
||||||
assert!(match_slot(&found, &program2, 11, 21));
|
assert!(match_slot(&extracted, &program2, 11, 21));
|
||||||
|
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(missing.contains(&(program4, 1)));
|
assert!(match_missing(&extracted, &program4, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Remove the expired entry to let the rest of the test continue
|
// Remove the expired entry to let the rest of the test continue
|
||||||
if let Some(programs) = cache.entries.get_mut(&program4) {
|
if let Some(programs) = cache.entries.get_mut(&program4) {
|
||||||
|
@ -1928,12 +1913,8 @@ mod tests {
|
||||||
// |
|
// |
|
||||||
// 23
|
// 23
|
||||||
|
|
||||||
// Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 21
|
// Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(21),
|
&TestWorkingSlot(21),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1945,19 +1926,14 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Since the fork was pruned, we should not find the entry deployed at slot 20.
|
// 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(&extracted, &program1, 0, 21));
|
||||||
assert!(match_slot(&found, &program2, 11, 21));
|
assert!(match_slot(&extracted, &program2, 11, 21));
|
||||||
assert!(match_slot(&found, &program4, 15, 21));
|
assert!(match_slot(&extracted, &program4, 15, 21));
|
||||||
|
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
|
// Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing: _,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(27),
|
&TestWorkingSlot(27),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -1968,11 +1944,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(unloaded.is_empty());
|
assert!(match_slot(&extracted, &program1, 0, 27));
|
||||||
assert!(match_slot(&found, &program1, 0, 27));
|
assert!(match_slot(&extracted, &program2, 11, 27));
|
||||||
assert!(match_slot(&found, &program2, 11, 27));
|
assert!(match_slot(&extracted, &program3, 25, 27));
|
||||||
assert!(match_slot(&found, &program3, 25, 27));
|
assert!(match_slot(&extracted, &program4, 5, 27));
|
||||||
assert!(match_slot(&found, &program4, 5, 27));
|
|
||||||
|
|
||||||
cache.prune(15, 0);
|
cache.prune(15, 0);
|
||||||
|
|
||||||
|
@ -1992,11 +1967,7 @@ mod tests {
|
||||||
// 23
|
// 23
|
||||||
|
|
||||||
// Testing fork 16, 19, 23, with root at 15, current slot at 23
|
// Testing fork 16, 19, 23, with root at 15, current slot at 23
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(23),
|
&TestWorkingSlot(23),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2007,13 +1978,12 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 23));
|
assert!(match_slot(&extracted, &program1, 0, 23));
|
||||||
assert!(match_slot(&found, &program2, 11, 23));
|
assert!(match_slot(&extracted, &program2, 11, 23));
|
||||||
assert!(match_slot(&found, &program4, 15, 23));
|
assert!(match_slot(&extracted, &program4, 15, 23));
|
||||||
|
|
||||||
// program3 was deployed on slot 25, which has been pruned
|
// program3 was deployed on slot 25, which has been pruned
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2055,11 +2025,7 @@ mod tests {
|
||||||
assert!(!cache.replenish(program3, new_test_loaded_program(25, 26)).0);
|
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
|
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(12),
|
&TestWorkingSlot(12),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2069,18 +2035,13 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 12));
|
assert!(match_slot(&extracted, &program1, 0, 12));
|
||||||
assert!(match_slot(&found, &program2, 11, 12));
|
assert!(match_slot(&extracted, &program2, 11, 12));
|
||||||
|
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Test the same fork, but request the program modified at a later slot than what's in the cache.
|
// Test the same fork, but request the program modified at a later slot than what's in the cache.
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(12),
|
&TestWorkingSlot(12),
|
||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
|
@ -2096,11 +2057,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program2, 11, 12));
|
assert!(match_slot(&extracted, &program2, 11, 12));
|
||||||
|
|
||||||
assert!(missing.contains(&(program1, 1)));
|
assert!(match_missing(&extracted, &program1, false));
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2159,11 +2119,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(19),
|
&TestWorkingSlot(19),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2173,18 +2129,13 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 19));
|
assert!(match_slot(&extracted, &program1, 0, 19));
|
||||||
assert!(match_slot(&found, &program2, 11, 19));
|
assert!(match_slot(&extracted, &program2, 11, 19));
|
||||||
|
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
|
// Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(27),
|
&TestWorkingSlot(27),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2194,18 +2145,13 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 27));
|
assert!(match_slot(&extracted, &program1, 0, 27));
|
||||||
assert!(match_slot(&found, &program2, 11, 27));
|
assert!(match_slot(&extracted, &program2, 11, 27));
|
||||||
|
|
||||||
assert!(unloaded.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, true));
|
||||||
assert!(missing.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 10 - 20 - 22 with current slot at 22
|
// Testing fork 0 - 10 - 20 - 22 with current slot at 22
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(22),
|
&TestWorkingSlot(22),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2215,10 +2161,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 20, 22));
|
assert!(match_slot(&extracted, &program1, 20, 22));
|
||||||
|
|
||||||
assert!(missing.contains(&(program2, 1)));
|
assert!(match_missing(&extracted, &program2, false));
|
||||||
assert!(unloaded.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2271,11 +2217,7 @@ mod tests {
|
||||||
assert!(!cache.replenish(program1, test_program).0);
|
assert!(!cache.replenish(program1, test_program).0);
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(12),
|
&TestWorkingSlot(12),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2286,19 +2228,14 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Program1 deployed at slot 11 should not be expired yet
|
// Program1 deployed at slot 11 should not be expired yet
|
||||||
assert!(match_slot(&found, &program1, 11, 12));
|
assert!(match_slot(&extracted, &program1, 11, 12));
|
||||||
assert!(match_slot(&found, &program2, 11, 12));
|
assert!(match_slot(&extracted, &program2, 11, 12));
|
||||||
|
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// Testing fork 0 - 5 - 11 - 12 - 15 - 16 - 19 - 21 - 23 with current slot at 15
|
// 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.
|
// This would cause program4 deployed at slot 15 to be expired.
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(15),
|
&TestWorkingSlot(15),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2307,12 +2244,11 @@ mod tests {
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.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!(match_missing(&extracted, &program1, false));
|
||||||
assert!(missing.contains(&(program3, 1)));
|
assert!(match_missing(&extracted, &program3, false));
|
||||||
|
|
||||||
// Test that the program still exists in the cache, even though it is expired.
|
// Test that the program still exists in the cache, even though it is expired.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -2366,19 +2302,17 @@ mod tests {
|
||||||
|
|
||||||
cache.prune(10, 0);
|
cache.prune(10, 0);
|
||||||
|
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing: _,
|
|
||||||
unloaded,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(20),
|
&TestWorkingSlot(20),
|
||||||
vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(),
|
vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(),
|
||||||
);
|
);
|
||||||
assert!(unloaded.is_empty());
|
|
||||||
|
|
||||||
// The cache should have the program deployed at slot 0
|
// The cache should have the program deployed at slot 0
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
found
|
extracted
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.loaded
|
||||||
.entries
|
.entries
|
||||||
.get(&program1)
|
.get(&program1)
|
||||||
.expect("Did not find the program")
|
.expect("Did not find the program")
|
||||||
|
@ -2414,11 +2348,7 @@ mod tests {
|
||||||
let program2 = Pubkey::new_unique();
|
let program2 = Pubkey::new_unique();
|
||||||
assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0);
|
assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0);
|
||||||
|
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing: _,
|
|
||||||
unloaded: _,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(20),
|
&TestWorkingSlot(20),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2427,14 +2357,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 20));
|
assert!(match_slot(&extracted, &program1, 0, 20));
|
||||||
assert!(match_slot(&found, &program2, 10, 20));
|
assert!(match_slot(&extracted, &program2, 10, 20));
|
||||||
|
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded: _,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(6),
|
&TestWorkingSlot(6),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2443,18 +2369,14 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 5, 6));
|
assert!(match_slot(&extracted, &program1, 5, 6));
|
||||||
assert!(missing.contains(&(program2, 1)));
|
assert!(match_missing(&extracted, &program2, false));
|
||||||
|
|
||||||
// Pruning slot 5 will remove program1 entry deployed at slot 5.
|
// 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.
|
// On fork chaining from slot 5, the entry deployed at slot 0 will become visible.
|
||||||
cache.prune_by_deployment_slot(5);
|
cache.prune_by_deployment_slot(5);
|
||||||
|
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing: _,
|
|
||||||
unloaded: _,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(20),
|
&TestWorkingSlot(20),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2463,14 +2385,10 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 20));
|
assert!(match_slot(&extracted, &program1, 0, 20));
|
||||||
assert!(match_slot(&found, &program2, 10, 20));
|
assert!(match_slot(&extracted, &program2, 10, 20));
|
||||||
|
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing,
|
|
||||||
unloaded: _,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(6),
|
&TestWorkingSlot(6),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2479,18 +2397,14 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 6));
|
assert!(match_slot(&extracted, &program1, 0, 6));
|
||||||
assert!(missing.contains(&(program2, 1)));
|
assert!(match_missing(&extracted, &program2, false));
|
||||||
|
|
||||||
// Pruning slot 10 will remove program2 entry deployed at slot 10.
|
// 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.
|
// As there is no other entry for program2, extract() will return it as missing.
|
||||||
cache.prune_by_deployment_slot(10);
|
cache.prune_by_deployment_slot(10);
|
||||||
|
|
||||||
let ExtractedPrograms {
|
let extracted = cache.extract(
|
||||||
loaded: found,
|
|
||||||
missing: _,
|
|
||||||
unloaded: _,
|
|
||||||
} = cache.extract(
|
|
||||||
&TestWorkingSlot(20),
|
&TestWorkingSlot(20),
|
||||||
vec![
|
vec![
|
||||||
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
|
||||||
|
@ -2499,8 +2413,8 @@ mod tests {
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(match_slot(&found, &program1, 0, 20));
|
assert!(match_slot(&extracted, &program1, 0, 20));
|
||||||
assert!(missing.contains(&(program2, 1)));
|
assert!(match_missing(&extracted, &program2, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -5071,28 +5071,23 @@ impl Bank {
|
||||||
let ExtractedPrograms {
|
let ExtractedPrograms {
|
||||||
loaded: mut loaded_programs_for_txs,
|
loaded: mut loaded_programs_for_txs,
|
||||||
missing,
|
missing,
|
||||||
unloaded,
|
|
||||||
} = {
|
} = {
|
||||||
// Lock the global cache to figure out which programs need to be loaded
|
// Lock the global cache to figure out which programs need to be loaded
|
||||||
let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
|
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
|
// Load missing programs while global cache is unlocked
|
||||||
let missing_programs: Vec<(Pubkey, Arc<LoadedProgram>)> = missing
|
let missing_programs: Vec<(Pubkey, Arc<LoadedProgram>)> = missing
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, count)| {
|
.map(|(key, (count, reloading))| {
|
||||||
let program = self.load_program(key, false, None);
|
let program = self.load_program(key, *reloading, 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<LoadedProgram>)> = unloaded
|
|
||||||
.iter()
|
|
||||||
.map(|(key, count)| {
|
|
||||||
let program = self.load_program(key, true, None);
|
|
||||||
program.tx_usage_counter.store(*count, Ordering::Relaxed);
|
program.tx_usage_counter.store(*count, Ordering::Relaxed);
|
||||||
(*key, program)
|
(*key, program)
|
||||||
})
|
})
|
||||||
|
@ -5105,12 +5100,6 @@ impl Bank {
|
||||||
// Use the returned entry as that might have been deduplicated globally
|
// Use the returned entry as that might have been deduplicated globally
|
||||||
loaded_programs_for_txs.replenish(key, entry);
|
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
|
loaded_programs_for_txs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue