Add a second level cache to contain tx batch specific programs (#31413)
* Add a second level cache to contain tx batch specific programs * fix clippy * address review comments
This commit is contained in:
parent
427ad7b5bd
commit
8eebf64464
|
@ -262,6 +262,38 @@ pub struct LoadedPrograms {
|
|||
entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LoadedProgramsForTxBatch {
|
||||
/// Pubkey is the address of a program.
|
||||
/// LoadedProgram is the corresponding program entry valid for the slot in which a transaction is being executed.
|
||||
entries: HashMap<Pubkey, Arc<LoadedProgram>>,
|
||||
slot: Slot,
|
||||
}
|
||||
|
||||
impl LoadedProgramsForTxBatch {
|
||||
/// Refill the cache with a single entry. It's typically called during transaction loading, and
|
||||
/// transaction processing (for program management instructions).
|
||||
/// The replaces the existing entry (if any) with the provided entry. The return value contains
|
||||
/// `true` if an entry existed.
|
||||
/// The function also returns the newly inserted value.
|
||||
pub fn replenish(
|
||||
&mut self,
|
||||
key: Pubkey,
|
||||
entry: Arc<LoadedProgram>,
|
||||
) -> (bool, Arc<LoadedProgram>) {
|
||||
debug_assert!(entry.effective_slot <= self.slot);
|
||||
(self.entries.insert(key, entry.clone()).is_some(), entry)
|
||||
}
|
||||
|
||||
pub fn find(&self, key: Pubkey) -> Option<Arc<LoadedProgram>> {
|
||||
self.entries.get(&key).cloned()
|
||||
}
|
||||
|
||||
pub fn slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
|
||||
fn example() -> Self {
|
||||
|
@ -373,7 +405,7 @@ impl LoadedPrograms {
|
|||
&self,
|
||||
working_slot: &S,
|
||||
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
|
||||
) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
|
||||
) -> (LoadedProgramsForTxBatch, Vec<Pubkey>) {
|
||||
let mut missing = Vec::new();
|
||||
let found = keys
|
||||
.filter_map(|(key, match_criteria)| {
|
||||
|
@ -410,7 +442,13 @@ impl LoadedPrograms {
|
|||
None
|
||||
})
|
||||
.collect();
|
||||
(found, missing)
|
||||
(
|
||||
LoadedProgramsForTxBatch {
|
||||
entries: found,
|
||||
slot: working_slot.current_slot(),
|
||||
},
|
||||
missing,
|
||||
)
|
||||
}
|
||||
|
||||
/// Evicts programs which were used infrequently
|
||||
|
@ -528,13 +566,12 @@ mod tests {
|
|||
use {
|
||||
crate::loaded_programs::{
|
||||
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
|
||||
LoadedPrograms, WorkingSlot,
|
||||
LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot,
|
||||
},
|
||||
percentage::Percentage,
|
||||
solana_rbpf::vm::BuiltInProgram,
|
||||
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
||||
std::{
|
||||
collections::HashMap,
|
||||
ops::ControlFlow,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
|
@ -1106,12 +1143,14 @@ mod tests {
|
|||
}
|
||||
|
||||
fn match_slot(
|
||||
table: &HashMap<Pubkey, Arc<LoadedProgram>>,
|
||||
table: &LoadedProgramsForTxBatch,
|
||||
program: &Pubkey,
|
||||
deployment_slot: Slot,
|
||||
working_slot: Slot,
|
||||
) -> bool {
|
||||
assert_eq!(table.slot, working_slot);
|
||||
table
|
||||
.get(program)
|
||||
.find(*program)
|
||||
.map(|entry| entry.deployment_slot == deployment_slot)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
@ -1189,8 +1228,8 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 20));
|
||||
assert!(match_slot(&found, &program4, 0));
|
||||
assert!(match_slot(&found, &program1, 20, 22));
|
||||
assert!(match_slot(&found, &program4, 0, 22));
|
||||
|
||||
assert!(missing.contains(&program2));
|
||||
assert!(missing.contains(&program3));
|
||||
|
@ -1208,11 +1247,11 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 0, 16));
|
||||
assert!(match_slot(&found, &program2, 11, 16));
|
||||
|
||||
// The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16.
|
||||
assert!(match_slot(&found, &program4, 5));
|
||||
assert!(match_slot(&found, &program4, 5, 16));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1229,11 +1268,11 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 0, 18));
|
||||
assert!(match_slot(&found, &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));
|
||||
assert!(match_slot(&found, &program4, 15, 18));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1250,11 +1289,11 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 0, 23));
|
||||
assert!(match_slot(&found, &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));
|
||||
assert!(match_slot(&found, &program4, 15, 23));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1271,9 +1310,9 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 5));
|
||||
assert!(match_slot(&found, &program4, 5));
|
||||
assert!(match_slot(&found, &program1, 0, 11));
|
||||
assert!(match_slot(&found, &program2, 5, 11));
|
||||
assert!(match_slot(&found, &program4, 5, 11));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1301,10 +1340,10 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 0, 19));
|
||||
assert!(match_slot(&found, &program2, 11, 19));
|
||||
// Program4 deployed at slot 19 should not be expired yet
|
||||
assert!(match_slot(&found, &program4, 19));
|
||||
assert!(match_slot(&found, &program4, 19, 19));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1322,8 +1361,8 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 0, 21));
|
||||
assert!(match_slot(&found, &program2, 11, 21));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
assert!(missing.contains(&program4));
|
||||
|
@ -1364,8 +1403,8 @@ mod tests {
|
|||
);
|
||||
|
||||
// Since the fork was pruned, we should not find the entry deployed at slot 20.
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program4, 0));
|
||||
assert!(match_slot(&found, &program1, 0, 22));
|
||||
assert!(match_slot(&found, &program4, 0, 22));
|
||||
|
||||
assert!(missing.contains(&program2));
|
||||
assert!(missing.contains(&program3));
|
||||
|
@ -1383,10 +1422,10 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program3, 25));
|
||||
assert!(match_slot(&found, &program4, 5));
|
||||
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));
|
||||
|
||||
cache.prune(&fork_graph, 15);
|
||||
|
||||
|
@ -1418,9 +1457,9 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program4, 5));
|
||||
assert!(match_slot(&found, &program1, 0, 27));
|
||||
assert!(match_slot(&found, &program2, 11, 27));
|
||||
assert!(match_slot(&found, &program4, 5, 27));
|
||||
|
||||
// program3 was deployed on slot 25, which has been pruned
|
||||
assert!(missing.contains(&program3));
|
||||
|
@ -1473,8 +1512,8 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program1, 0));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 0, 12));
|
||||
assert!(match_slot(&found, &program2, 11, 12));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1495,7 +1534,7 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program2, 11, 12));
|
||||
|
||||
assert!(missing.contains(&program1));
|
||||
assert!(missing.contains(&program3));
|
||||
|
@ -1560,8 +1599,8 @@ mod tests {
|
|||
);
|
||||
|
||||
// Program1 deployed at slot 11 should not be expired yet
|
||||
assert!(match_slot(&found, &program1, 11));
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program1, 11, 12));
|
||||
assert!(match_slot(&found, &program2, 11, 12));
|
||||
|
||||
assert!(missing.contains(&program3));
|
||||
|
||||
|
@ -1578,7 +1617,7 @@ mod tests {
|
|||
.into_iter(),
|
||||
);
|
||||
|
||||
assert!(match_slot(&found, &program2, 11));
|
||||
assert!(match_slot(&found, &program2, 11, 15));
|
||||
|
||||
assert!(missing.contains(&program1));
|
||||
assert!(missing.contains(&program3));
|
||||
|
@ -1642,6 +1681,7 @@ mod tests {
|
|||
// The cache should have the program deployed at slot 0
|
||||
assert_eq!(
|
||||
found
|
||||
.entries
|
||||
.get(&program1)
|
||||
.expect("Did not find the program")
|
||||
.deployment_slot,
|
||||
|
|
|
@ -97,7 +97,10 @@ use {
|
|||
builtin_program::{BuiltinProgram, BuiltinPrograms, ProcessInstructionWithContext},
|
||||
compute_budget::{self, ComputeBudget},
|
||||
executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS},
|
||||
loaded_programs::{LoadedProgram, LoadedProgramType, LoadedPrograms, WorkingSlot},
|
||||
loaded_programs::{
|
||||
LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadedPrograms,
|
||||
LoadedProgramsForTxBatch, WorkingSlot,
|
||||
},
|
||||
log_collector::LogCollector,
|
||||
sysvar_cache::SysvarCache,
|
||||
timings::{ExecuteTimingType, ExecuteTimings},
|
||||
|
@ -258,7 +261,6 @@ pub struct BankRc {
|
|||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
use solana_frozen_abi::abi_example::AbiExample;
|
||||
use solana_program_runtime::loaded_programs::LoadedProgramMatchCriteria;
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
impl AbiExample for BankRc {
|
||||
|
@ -4427,7 +4429,7 @@ impl Bank {
|
|||
fn load_and_get_programs_from_cache(
|
||||
&self,
|
||||
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
|
||||
) -> HashMap<Pubkey, Arc<LoadedProgram>> {
|
||||
) -> LoadedProgramsForTxBatch {
|
||||
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
|
||||
if self.check_program_modification_slot {
|
||||
program_accounts_map
|
||||
|
@ -4476,7 +4478,7 @@ impl Bank {
|
|||
for (key, program) in missing_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.insert(key, entry);
|
||||
loaded_programs_for_txs.replenish(key, entry);
|
||||
}
|
||||
|
||||
loaded_programs_for_txs
|
||||
|
|
Loading…
Reference in New Issue