Retain runtime environment config for unloaded programs (#31953)
* Retain runtime environment config for unloaded programs
This commit is contained in:
parent
40d3e8e8fa
commit
1b30de4f32
|
@ -65,12 +65,12 @@ pub enum LoadedProgramType {
|
||||||
Closed,
|
Closed,
|
||||||
DelayVisibility,
|
DelayVisibility,
|
||||||
/// Successfully verified but not currently compiled, used to track usage statistics when a compiled program is evicted from memory.
|
/// Successfully verified but not currently compiled, used to track usage statistics when a compiled program is evicted from memory.
|
||||||
Unloaded,
|
Unloaded(Arc<BuiltinProgram<InvokeContext<'static>>>),
|
||||||
LegacyV0(Executable<RequisiteVerifier, InvokeContext<'static>>),
|
LegacyV0(Executable<RequisiteVerifier, InvokeContext<'static>>),
|
||||||
LegacyV1(Executable<RequisiteVerifier, InvokeContext<'static>>),
|
LegacyV1(Executable<RequisiteVerifier, InvokeContext<'static>>),
|
||||||
Typed(Executable<RequisiteVerifier, InvokeContext<'static>>),
|
Typed(Executable<RequisiteVerifier, InvokeContext<'static>>),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
TestLoaded,
|
TestLoaded(Arc<BuiltinProgram<InvokeContext<'static>>>),
|
||||||
Builtin(BuiltinProgram<InvokeContext<'static>>),
|
Builtin(BuiltinProgram<InvokeContext<'static>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +82,12 @@ impl Debug for LoadedProgramType {
|
||||||
}
|
}
|
||||||
LoadedProgramType::Closed => write!(f, "LoadedProgramType::Closed"),
|
LoadedProgramType::Closed => write!(f, "LoadedProgramType::Closed"),
|
||||||
LoadedProgramType::DelayVisibility => write!(f, "LoadedProgramType::DelayVisibility"),
|
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::LegacyV0(_) => write!(f, "LoadedProgramType::LegacyV0"),
|
||||||
LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"),
|
LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"),
|
||||||
LoadedProgramType::Typed(_) => write!(f, "LoadedProgramType::Typed"),
|
LoadedProgramType::Typed(_) => write!(f, "LoadedProgramType::Typed"),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
LoadedProgramType::TestLoaded => write!(f, "LoadedProgramType::TestLoaded"),
|
LoadedProgramType::TestLoaded(_) => write!(f, "LoadedProgramType::TestLoaded"),
|
||||||
LoadedProgramType::Builtin(_) => write!(f, "LoadedProgramType::Builtin"),
|
LoadedProgramType::Builtin(_) => write!(f, "LoadedProgramType::Builtin"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,16 +272,24 @@ impl LoadedProgram {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_unloaded(&self) -> Self {
|
pub fn to_unloaded(&self) -> Option<Self> {
|
||||||
Self {
|
let env = match &self.program {
|
||||||
program: LoadedProgramType::Unloaded,
|
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,
|
account_size: self.account_size,
|
||||||
deployment_slot: self.deployment_slot,
|
deployment_slot: self.deployment_slot,
|
||||||
effective_slot: self.effective_slot,
|
effective_slot: self.effective_slot,
|
||||||
maybe_expiration_slot: self.maybe_expiration_slot,
|
maybe_expiration_slot: self.maybe_expiration_slot,
|
||||||
tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
|
tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
|
||||||
ix_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
|
/// 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 {
|
fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
|
||||||
!matches!(self.program, LoadedProgramType::Builtin(_))
|
!matches!(self.program, LoadedProgramType::Builtin(_))
|
||||||
&& self.effective_slot.saturating_sub(self.deployment_slot)
|
&& self.effective_slot.saturating_sub(self.deployment_slot)
|
||||||
|
@ -447,7 +444,7 @@ impl LoadedPrograms {
|
||||||
if existing.deployment_slot == entry.deployment_slot
|
if existing.deployment_slot == entry.deployment_slot
|
||||||
&& existing.effective_slot == entry.effective_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
|
// The unloaded program is getting reloaded
|
||||||
// Copy over the usage counter to the new entry
|
// Copy over the usage counter to the new entry
|
||||||
let mut usage_count = existing.tx_usage_counter.load(Ordering::Relaxed);
|
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)
|
Self::matches_loaded_program_criteria(entry, match_criteria)
|
||||||
// If the program was unloaded. Consider it as unusable, so it can be reloaded.
|
// 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
|
/// Extracts a subset of the programs relevant to a transaction batch
|
||||||
|
@ -636,8 +633,8 @@ impl LoadedPrograms {
|
||||||
| LoadedProgramType::LegacyV1(_)
|
| LoadedProgramType::LegacyV1(_)
|
||||||
| LoadedProgramType::Typed(_) => Some((*id, program.clone())),
|
| LoadedProgramType::Typed(_) => Some((*id, program.clone())),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
LoadedProgramType::TestLoaded => Some((*id, program.clone())),
|
LoadedProgramType::TestLoaded(_) => Some((*id, program.clone())),
|
||||||
LoadedProgramType::Unloaded
|
LoadedProgramType::Unloaded(_)
|
||||||
| LoadedProgramType::FailedVerification
|
| LoadedProgramType::FailedVerification
|
||||||
| LoadedProgramType::Closed
|
| LoadedProgramType::Closed
|
||||||
| LoadedProgramType::DelayVisibility
|
| LoadedProgramType::DelayVisibility
|
||||||
|
@ -682,8 +679,8 @@ impl LoadedPrograms {
|
||||||
fn unload_program(&mut self, id: &Pubkey) {
|
fn unload_program(&mut self, id: &Pubkey) {
|
||||||
if let Some(entries) = self.entries.get_mut(id) {
|
if let Some(entries) = self.entries.get_mut(id) {
|
||||||
entries.iter_mut().for_each(|entry| {
|
entries.iter_mut().for_each(|entry| {
|
||||||
if entry.is_loaded() {
|
if let Some(unloaded) = entry.to_unloaded() {
|
||||||
*entry = Arc::new(entry.to_unloaded());
|
*entry = Arc::new(unloaded);
|
||||||
self.stats
|
self.stats
|
||||||
.evictions
|
.evictions
|
||||||
.entry(*id)
|
.entry(*id)
|
||||||
|
@ -706,15 +703,17 @@ impl LoadedPrograms {
|
||||||
for (id, program) in remove {
|
for (id, program) in remove {
|
||||||
if let Some(entries) = self.entries.get_mut(id) {
|
if let Some(entries) = self.entries.get_mut(id) {
|
||||||
if let Some(candidate) = entries.iter_mut().find(|entry| entry == &program) {
|
if let Some(candidate) = entries.iter_mut().find(|entry| entry == &program) {
|
||||||
if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
|
if let Some(unloaded) = candidate.to_unloaded() {
|
||||||
self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
|
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,
|
LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET,
|
||||||
},
|
},
|
||||||
percentage::Percentage,
|
percentage::Percentage,
|
||||||
solana_rbpf::vm::BuiltinProgram,
|
solana_rbpf::vm::{BuiltinProgram, Config},
|
||||||
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
||||||
std::{
|
std::{
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
|
@ -793,8 +792,11 @@ mod tests {
|
||||||
key: Pubkey,
|
key: Pubkey,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
) -> Arc<LoadedProgram> {
|
) -> Arc<LoadedProgram> {
|
||||||
let unloaded =
|
let unloaded = Arc::new(
|
||||||
Arc::new(new_test_loaded_program(slot, slot.saturating_add(1)).to_unloaded());
|
new_test_loaded_program(slot, slot.saturating_add(1))
|
||||||
|
.to_unloaded()
|
||||||
|
.expect("Failed to unload the program"),
|
||||||
|
);
|
||||||
cache.replenish(key, unloaded).1
|
cache.replenish(key, unloaded).1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -918,10 +920,10 @@ mod tests {
|
||||||
programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
|
programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
|
||||||
|
|
||||||
let num_loaded = num_matching_entries(&cache, |program_type| {
|
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| {
|
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| {
|
let num_tombstones = num_matching_entries(&cache, |program_type| {
|
||||||
matches!(
|
matches!(
|
||||||
|
@ -951,7 +953,7 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(id, cached_programs)| {
|
.flat_map(|(id, cached_programs)| {
|
||||||
cached_programs.iter().filter_map(|program| {
|
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)))
|
.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| {
|
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| {
|
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| {
|
let num_tombstones = num_matching_entries(&cache, |program_type| {
|
||||||
matches!(
|
matches!(
|
||||||
|
@ -999,13 +1001,13 @@ mod tests {
|
||||||
cache.sort_and_unload(Percentage::from(2));
|
cache.sort_and_unload(Percentage::from(2));
|
||||||
|
|
||||||
let num_unloaded = num_matching_entries(&cache, |program_type| {
|
let num_unloaded = num_matching_entries(&cache, |program_type| {
|
||||||
matches!(program_type, LoadedProgramType::Unloaded)
|
matches!(program_type, LoadedProgramType::Unloaded(_))
|
||||||
});
|
});
|
||||||
assert_eq!(num_unloaded, 1);
|
assert_eq!(num_unloaded, 1);
|
||||||
|
|
||||||
cache.entries.values().for_each(|programs| {
|
cache.entries.values().for_each(|programs| {
|
||||||
programs.iter().for_each(|program| {
|
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
|
// Test that the usage counter is retained for the unloaded program
|
||||||
assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
|
assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
|
||||||
assert_eq!(program.deployment_slot, 0);
|
assert_eq!(program.deployment_slot, 0);
|
||||||
|
@ -1023,7 +1025,7 @@ mod tests {
|
||||||
|
|
||||||
cache.entries.values().for_each(|programs| {
|
cache.entries.values().for_each(|programs| {
|
||||||
programs.iter().for_each(|program| {
|
programs.iter().for_each(|program| {
|
||||||
if matches!(program.program, LoadedProgramType::Unloaded)
|
if matches!(program.program, LoadedProgramType::Unloaded(_))
|
||||||
&& program.deployment_slot == 0
|
&& program.deployment_slot == 0
|
||||||
&& program.effective_slot == 2
|
&& program.effective_slot == 2
|
||||||
{
|
{
|
||||||
|
@ -1259,17 +1261,33 @@ mod tests {
|
||||||
fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc<LoadedProgram> {
|
fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc<LoadedProgram> {
|
||||||
new_test_loaded_program_with_usage(deployment_slot, effective_slot, AtomicU64::default())
|
new_test_loaded_program_with_usage(deployment_slot, effective_slot, AtomicU64::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_test_loaded_program_with_usage(
|
fn new_test_loaded_program_with_usage(
|
||||||
deployment_slot: Slot,
|
deployment_slot: Slot,
|
||||||
effective_slot: Slot,
|
effective_slot: Slot,
|
||||||
usage_counter: AtomicU64,
|
usage_counter: AtomicU64,
|
||||||
) -> Arc<LoadedProgram> {
|
) -> Arc<LoadedProgram> {
|
||||||
|
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<Slot>,
|
||||||
|
) -> Arc<LoadedProgram> {
|
||||||
|
let env = Arc::new(BuiltinProgram::new_loader(Config::default()));
|
||||||
Arc::new(LoadedProgram {
|
Arc::new(LoadedProgram {
|
||||||
program: LoadedProgramType::TestLoaded,
|
program: LoadedProgramType::TestLoaded(env),
|
||||||
account_size: 0,
|
account_size: 0,
|
||||||
deployment_slot,
|
deployment_slot,
|
||||||
effective_slot,
|
effective_slot,
|
||||||
maybe_expiration_slot: None,
|
maybe_expiration_slot: expiry,
|
||||||
tx_usage_counter: usage_counter,
|
tx_usage_counter: usage_counter,
|
||||||
ix_usage_counter: AtomicU64::default(),
|
ix_usage_counter: AtomicU64::default(),
|
||||||
})
|
})
|
||||||
|
@ -1852,15 +1870,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_usable_entries_for_slot() {
|
fn test_usable_entries_for_slot() {
|
||||||
let unloaded_entry = Arc::new(LoadedProgram {
|
let unloaded_entry = Arc::new(
|
||||||
program: LoadedProgramType::Unloaded,
|
new_test_loaded_program(0, 0)
|
||||||
account_size: 0,
|
.to_unloaded()
|
||||||
deployment_slot: 0,
|
.expect("Failed to unload the program"),
|
||||||
effective_slot: 0,
|
);
|
||||||
maybe_expiration_slot: None,
|
|
||||||
tx_usage_counter: AtomicU64::default(),
|
|
||||||
ix_usage_counter: AtomicU64::default(),
|
|
||||||
});
|
|
||||||
assert!(!LoadedPrograms::is_entry_usable(
|
assert!(!LoadedPrograms::is_entry_usable(
|
||||||
&unloaded_entry,
|
&unloaded_entry,
|
||||||
0,
|
0,
|
||||||
|
@ -1949,15 +1963,12 @@ mod tests {
|
||||||
&LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1)
|
&LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1)
|
||||||
));
|
));
|
||||||
|
|
||||||
let program = Arc::new(LoadedProgram {
|
let program = Arc::new(new_test_loaded_program_with_usage_and_expiry(
|
||||||
program: LoadedProgramType::TestLoaded,
|
0,
|
||||||
account_size: 0,
|
1,
|
||||||
deployment_slot: 0,
|
AtomicU64::default(),
|
||||||
effective_slot: 1,
|
Some(2),
|
||||||
maybe_expiration_slot: Some(2),
|
));
|
||||||
tx_usage_counter: AtomicU64::default(),
|
|
||||||
ix_usage_counter: AtomicU64::default(),
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(LoadedPrograms::is_entry_usable(
|
assert!(LoadedPrograms::is_entry_usable(
|
||||||
&program,
|
&program,
|
||||||
|
|
|
@ -4128,8 +4128,9 @@ mod tests {
|
||||||
let transaction_accounts = vec![];
|
let transaction_accounts = vec![];
|
||||||
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
||||||
let program_id = Pubkey::new_unique();
|
let program_id = Pubkey::new_unique();
|
||||||
|
let env = Arc::new(BuiltinProgram::new_loader(Config::default()));
|
||||||
let program = LoadedProgram {
|
let program = LoadedProgram {
|
||||||
program: LoadedProgramType::Unloaded,
|
program: LoadedProgramType::Unloaded(env),
|
||||||
account_size: 0,
|
account_size: 0,
|
||||||
deployment_slot: 0,
|
deployment_slot: 0,
|
||||||
effective_slot: 0,
|
effective_slot: 0,
|
||||||
|
@ -4167,8 +4168,9 @@ mod tests {
|
||||||
let transaction_accounts = vec![];
|
let transaction_accounts = vec![];
|
||||||
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
||||||
let program_id = Pubkey::new_unique();
|
let program_id = Pubkey::new_unique();
|
||||||
|
let env = Arc::new(BuiltinProgram::new_loader(Config::default()));
|
||||||
let program = LoadedProgram {
|
let program = LoadedProgram {
|
||||||
program: LoadedProgramType::Unloaded,
|
program: LoadedProgramType::Unloaded(env),
|
||||||
account_size: 0,
|
account_size: 0,
|
||||||
deployment_slot: 0,
|
deployment_slot: 0,
|
||||||
effective_slot: 0,
|
effective_slot: 0,
|
||||||
|
|
Loading…
Reference in New Issue