Evict program cache using transaction usage counter (#31927)

* Evict program cache using tx usage counter

* address review feedback

* address review feedback
This commit is contained in:
Pankaj Garg 2023-06-02 11:26:56 -07:00 committed by GitHub
parent 4b0514d9b1
commit 37ebb709e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 138 deletions

View File

@ -735,7 +735,7 @@ impl<'a> InvokeContext<'a> {
_ => None,
}
.ok_or(InstructionError::UnsupportedProgramId)?;
entry.usage_counter.fetch_add(1, Ordering::Relaxed);
entry.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
let program_id = *instruction_context.get_last_program_key(self.transaction_context)?;
self.transaction_context

View File

@ -105,8 +105,10 @@ pub struct LoadedProgram {
pub effective_slot: Slot,
/// Optional expiration slot for this entry, after which it is treated as non-existent
pub maybe_expiration_slot: Option<Slot>,
/// How often this entry was used
pub usage_counter: AtomicU64,
/// How often this entry was used by a transaction
pub tx_usage_counter: AtomicU64,
/// How often this entry was used by a transaction
pub ix_usage_counter: AtomicU64,
}
#[derive(Debug, Default)]
@ -264,8 +266,9 @@ impl LoadedProgram {
account_size,
effective_slot,
maybe_expiration_slot,
usage_counter: AtomicU64::new(0),
tx_usage_counter: AtomicU64::new(0),
program,
ix_usage_counter: AtomicU64::new(0),
})
}
@ -276,7 +279,8 @@ impl LoadedProgram {
deployment_slot: self.deployment_slot,
effective_slot: self.effective_slot,
maybe_expiration_slot: self.maybe_expiration_slot,
usage_counter: AtomicU64::new(self.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)),
}
}
@ -295,8 +299,9 @@ impl LoadedProgram {
account_size,
effective_slot: deployment_slot,
maybe_expiration_slot: None,
usage_counter: AtomicU64::new(0),
tx_usage_counter: AtomicU64::new(0),
program: LoadedProgramType::Builtin(program),
ix_usage_counter: AtomicU64::new(0),
}
}
@ -309,7 +314,8 @@ impl LoadedProgram {
deployment_slot: slot,
effective_slot: slot,
maybe_expiration_slot,
usage_counter: AtomicU64::default(),
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
};
debug_assert!(tombstone.is_tombstone());
tombstone
@ -444,8 +450,14 @@ impl LoadedPrograms {
if matches!(existing.program, LoadedProgramType::Unloaded) {
// The unloaded program is getting reloaded
// Copy over the usage counter to the new entry
entry.usage_counter.store(
existing.usage_counter.load(Ordering::Relaxed),
let mut usage_count = existing.tx_usage_counter.load(Ordering::Relaxed);
saturating_add_assign!(
usage_count,
entry.tx_usage_counter.load(Ordering::Relaxed)
);
entry.tx_usage_counter.store(usage_count, Ordering::Relaxed);
entry.ix_usage_counter.store(
existing.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
second_level.swap_remove(entry_index);
@ -548,11 +560,11 @@ impl LoadedPrograms {
pub fn extract<S: WorkingSlot>(
&self,
working_slot: &S,
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
) -> (LoadedProgramsForTxBatch, Vec<Pubkey>) {
keys: impl Iterator<Item = (Pubkey, (LoadedProgramMatchCriteria, u64))>,
) -> (LoadedProgramsForTxBatch, Vec<(Pubkey, u64)>) {
let mut missing = Vec::new();
let found = keys
.filter_map(|(key, match_criteria)| {
.filter_map(|(key, (match_criteria, count))| {
if let Some(second_level) = self.entries.get(&key) {
for entry in second_level.iter().rev() {
let current_slot = working_slot.current_slot();
@ -561,11 +573,15 @@ impl LoadedPrograms {
|| working_slot.is_ancestor(entry.deployment_slot)
{
if !Self::is_entry_usable(entry, current_slot, &match_criteria) {
missing.push(key);
missing.push((key, count));
return None;
}
if current_slot >= entry.effective_slot {
let mut usage_count =
entry.tx_usage_counter.load(Ordering::Relaxed);
saturating_add_assign!(usage_count, count);
entry.tx_usage_counter.store(usage_count, Ordering::Relaxed);
return Some((key, entry.clone()));
} else if entry.is_implicit_delay_visibility_tombstone(current_slot) {
// Found a program entry on the current fork, but it's not effective
@ -582,7 +598,7 @@ impl LoadedPrograms {
}
}
}
missing.push(key);
missing.push((key, count));
None
})
.collect::<HashMap<Pubkey, Arc<LoadedProgram>>>();
@ -628,7 +644,7 @@ impl LoadedPrograms {
| LoadedProgramType::Builtin(_) => None,
})
})
.sorted_by_cached_key(|(_id, program)| program.usage_counter.load(Ordering::Relaxed))
.sorted_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed))
.collect();
let num_to_unload = sorted_candidates
@ -690,7 +706,7 @@ impl LoadedPrograms {
for (id, program) in remove {
if let Some(entries) = self.entries.get_mut(id) {
if let Some(candidate) = entries.iter_mut().find(|entry| entry == &program) {
if candidate.usage_counter.load(Ordering::Relaxed) == 1 {
if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
}
self.stats
@ -758,7 +774,8 @@ mod tests {
deployment_slot,
effective_slot,
maybe_expiration_slot: None,
usage_counter: AtomicU64::default(),
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
})
}
@ -935,7 +952,7 @@ mod tests {
.flat_map(|(id, cached_programs)| {
cached_programs.iter().filter_map(|program| {
matches!(program.program, LoadedProgramType::Unloaded)
.then_some((*id, program.usage_counter.load(Ordering::Relaxed)))
.then_some((*id, program.tx_usage_counter.load(Ordering::Relaxed)))
})
})
.collect::<Vec<(Pubkey, u64)>>();
@ -990,7 +1007,7 @@ mod tests {
programs.iter().for_each(|program| {
if matches!(program.program, LoadedProgramType::Unloaded) {
// Test that the usage counter is retained for the unloaded program
assert_eq!(program.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.effective_slot, 2);
}
@ -1011,7 +1028,7 @@ mod tests {
&& program.effective_slot == 2
{
// Test that the usage counter was correctly updated.
assert_eq!(program.usage_counter.load(Ordering::Relaxed), 10);
assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
}
})
});
@ -1253,7 +1270,8 @@ mod tests {
deployment_slot,
effective_slot,
maybe_expiration_slot: None,
usage_counter,
tx_usage_counter: usage_counter,
ix_usage_counter: AtomicU64::default(),
})
}
@ -1349,10 +1367,10 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 2)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 3)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 4)),
]
.into_iter(),
);
@ -1360,18 +1378,18 @@ mod tests {
assert!(match_slot(&found, &program1, 20, 22));
assert!(match_slot(&found, &program4, 0, 22));
assert!(missing.contains(&program2));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program2, 2)));
assert!(missing.contains(&(program3, 3)));
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16
let mut working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 23]);
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1388,17 +1406,17 @@ mod tests {
));
assert_eq!(tombstone.deployment_slot, 15);
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
working_slot.update_slot(18);
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1409,17 +1427,17 @@ mod tests {
// 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!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
working_slot.update_slot(23);
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1430,17 +1448,17 @@ mod tests {
// 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!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]);
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1455,7 +1473,7 @@ mod tests {
assert_eq!(tombstone.deployment_slot, 11);
assert!(match_slot(&found, &program4, 5, 11));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// The following is a special case, where there's an expiration slot
let test_program = Arc::new(LoadedProgram {
@ -1464,7 +1482,8 @@ mod tests {
deployment_slot: 19,
effective_slot: 19,
maybe_expiration_slot: Some(21),
usage_counter: AtomicU64::default(),
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
});
assert!(!cache.replenish(program4, test_program).0);
@ -1473,10 +1492,10 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1486,7 +1505,7 @@ mod tests {
// Program4 deployed at slot 19 should not be expired yet
assert!(match_slot(&found, &program4, 19, 19));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// 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.
@ -1494,10 +1513,10 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1505,8 +1524,8 @@ mod tests {
assert!(match_slot(&found, &program1, 0, 21));
assert!(match_slot(&found, &program2, 11, 21));
assert!(missing.contains(&program3));
assert!(missing.contains(&program4));
assert!(missing.contains(&(program3, 1)));
assert!(missing.contains(&(program4, 1)));
// Remove the expired entry to let the rest of the test continue
if let Some(programs) = cache.entries.get_mut(&program4) {
@ -1535,10 +1554,10 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1548,17 +1567,17 @@ mod tests {
assert!(match_slot(&found, &program2, 11, 22));
assert!(match_slot(&found, &program4, 15, 22));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
let working_slot = TestWorkingSlot::new(27, &[11, 25, 27]);
let (found, _missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1590,10 +1609,10 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1603,7 +1622,7 @@ mod tests {
assert!(match_slot(&found, &program4, 15, 23));
// program3 was deployed on slot 25, which has been pruned
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
}
#[test]
@ -1646,9 +1665,9 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1656,7 +1675,7 @@ mod tests {
assert!(match_slot(&found, &program1, 0, 12));
assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// Test the same fork, but request the program modified at a later slot than what's in the cache.
let (found, missing) = cache.extract(
@ -1664,21 +1683,21 @@ mod tests {
vec![
(
program1,
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5),
(LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1),
),
(
program2,
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5),
(LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1),
),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&program1));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program1, 1)));
assert!(missing.contains(&(program3, 1)));
}
#[test]
@ -1723,7 +1742,8 @@ mod tests {
deployment_slot: 11,
effective_slot: 11,
maybe_expiration_slot: Some(15),
usage_counter: AtomicU64::default(),
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
});
assert!(!cache.replenish(program1, test_program).0);
@ -1732,9 +1752,9 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
@ -1743,7 +1763,7 @@ mod tests {
assert!(match_slot(&found, &program1, 11, 12));
assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program3, 1)));
// 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.
@ -1751,17 +1771,17 @@ mod tests {
let (found, missing) = cache.extract(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
(program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
]
.into_iter(),
);
assert!(match_slot(&found, &program2, 11, 15));
assert!(missing.contains(&program1));
assert!(missing.contains(&program3));
assert!(missing.contains(&(program1, 1)));
assert!(missing.contains(&(program3, 1)));
// Test that the program still exists in the cache, even though it is expired.
assert_eq!(
@ -1816,7 +1836,7 @@ mod tests {
let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]);
let (found, _missing) = cache.extract(
&working_slot,
vec![(program1, LoadedProgramMatchCriteria::NoCriteria)].into_iter(),
vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(),
);
// The cache should have the program deployed at slot 0
@ -1838,7 +1858,8 @@ mod tests {
deployment_slot: 0,
effective_slot: 0,
maybe_expiration_slot: None,
usage_counter: AtomicU64::default(),
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
});
assert!(!LoadedPrograms::is_entry_usable(
&unloaded_entry,
@ -1934,7 +1955,8 @@ mod tests {
deployment_slot: 0,
effective_slot: 1,
maybe_expiration_slot: Some(2),
usage_counter: AtomicU64::default(),
tx_usage_counter: AtomicU64::default(),
ix_usage_counter: AtomicU64::default(),
});
assert!(LoadedPrograms::is_entry_usable(

View File

@ -205,8 +205,14 @@ macro_rules! deploy_program {
Arc::new(program_runtime_environment),
)?;
if let Some(old_entry) = find_program_in_cache($invoke_context, &$program_id) {
let usage_counter = old_entry.usage_counter.load(Ordering::Relaxed);
executor.usage_counter.store(usage_counter, Ordering::Relaxed);
executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed
);
executor.ix_usage_counter.store(
old_entry.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed
);
}
$drop
load_program_metrics.program_id = $program_id.to_string();
@ -562,7 +568,7 @@ fn process_instruction_inner(
get_or_create_executor_time.as_us()
);
executor.usage_counter.fetch_add(1, Ordering::Relaxed);
executor.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
match &executor.program {
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
@ -4128,7 +4134,8 @@ mod tests {
deployment_slot: 0,
effective_slot: 0,
maybe_expiration_slot: None,
usage_counter: AtomicU64::new(100),
tx_usage_counter: AtomicU64::new(100),
ix_usage_counter: AtomicU64::new(100),
};
invoke_context
.programs_modified_by_tx
@ -4145,7 +4152,14 @@ mod tests {
.expect("Didn't find upgraded program in the cache");
assert_eq!(updated_program.deployment_slot, 2);
assert_eq!(updated_program.usage_counter.load(Ordering::Relaxed), 100);
assert_eq!(
updated_program.tx_usage_counter.load(Ordering::Relaxed),
100
);
assert_eq!(
updated_program.ix_usage_counter.load(Ordering::Relaxed),
100
);
}
#[test]
@ -4159,7 +4173,8 @@ mod tests {
deployment_slot: 0,
effective_slot: 0,
maybe_expiration_slot: None,
usage_counter: AtomicU64::new(100),
tx_usage_counter: AtomicU64::new(100),
ix_usage_counter: AtomicU64::new(100),
};
invoke_context
.programs_modified_by_tx
@ -4177,6 +4192,7 @@ mod tests {
.expect("Didn't find upgraded program in the cache");
assert_eq!(program2.deployment_slot, 2);
assert_eq!(program2.usage_counter.load(Ordering::Relaxed), 0);
assert_eq!(program2.tx_usage_counter.load(Ordering::Relaxed), 0);
assert_eq!(program2.ix_usage_counter.load(Ordering::Relaxed), 0);
}
}

View File

@ -608,7 +608,9 @@ pub fn process_instruction_inner(
get_or_create_executor_time.as_us()
);
drop(program);
loaded_program.usage_counter.fetch_add(1, Ordering::Relaxed);
loaded_program
.ix_usage_counter
.fetch_add(1, Ordering::Relaxed);
match &loaded_program.program {
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed

View File

@ -62,7 +62,10 @@ use {
solana_system_program::{get_system_account_kind, SystemAccountKind},
std::{
cmp::Reverse,
collections::{hash_map, BinaryHeap, HashMap, HashSet},
collections::{
hash_map::{self, Entry},
BinaryHeap, HashMap, HashSet,
},
num::NonZeroUsize,
ops::RangeBounds,
path::PathBuf,
@ -291,7 +294,7 @@ impl Accounts {
key: &Pubkey,
feature_set: &FeatureSet,
program: &LoadedProgram,
program_accounts: &HashMap<Pubkey, &Pubkey>,
program_accounts: &HashMap<Pubkey, (&Pubkey, u64)>,
) -> Result<AccountSharedData> {
// Check for tombstone
let result = match &program.program {
@ -314,7 +317,7 @@ impl Accounts {
// So the account data is not needed. Return a dummy AccountSharedData with meta
// information.
let mut program_account = AccountSharedData::default();
let program_owner = program_accounts
let (program_owner, _count) = program_accounts
.get(key)
.ok_or(TransactionError::AccountNotFound)?;
program_account.set_owner(**program_owner);
@ -333,7 +336,7 @@ impl Accounts {
feature_set: &FeatureSet,
account_overrides: Option<&AccountOverrides>,
_reward_interval: RewardInterval,
program_accounts: &HashMap<Pubkey, &Pubkey>,
program_accounts: &HashMap<Pubkey, (&Pubkey, u64)>,
loaded_programs: &LoadedProgramsForTxBatch,
) -> Result<LoadedTransaction> {
// NOTE: this check will never fail because `tx` is sanitized
@ -637,8 +640,8 @@ impl Accounts {
lock_results: &mut [TransactionCheckResult],
program_owners: &[&'a Pubkey],
hash_queue: &BlockhashQueue,
) -> HashMap<Pubkey, &'a Pubkey> {
let mut result = HashMap::new();
) -> HashMap<Pubkey, (&'a Pubkey, u64)> {
let mut result: HashMap<Pubkey, (&'a Pubkey, u64)> = HashMap::new();
lock_results.iter_mut().zip(txs).for_each(|etx| {
if let ((Ok(()), nonce), tx) = etx {
if nonce
@ -649,19 +652,26 @@ impl Accounts {
})
.is_some()
{
tx.message().account_keys().iter().for_each(|key| {
if !result.contains_key(key) {
if let Ok(index) = self.accounts_db.account_matches_owners(
ancestors,
key,
program_owners,
) {
program_owners
.get(index)
.and_then(|owner| result.insert(*key, *owner));
tx.message()
.account_keys()
.iter()
.for_each(|key| match result.entry(*key) {
Entry::Occupied(mut entry) => {
let (_, count) = entry.get_mut();
saturating_add_assign!(*count, 1);
}
}
});
Entry::Vacant(entry) => {
if let Ok(index) = self.accounts_db.account_matches_owners(
ancestors,
key,
program_owners,
) {
program_owners
.get(index)
.map(|owner| entry.insert((*owner, 1)));
}
}
});
} else {
// If the transaction's nonce account was not valid, and blockhash is not found,
// the transaction will fail to process. Let's not load any programs from the
@ -686,7 +696,7 @@ impl Accounts {
fee_structure: &FeeStructure,
account_overrides: Option<&AccountOverrides>,
in_reward_interval: RewardInterval,
program_accounts: &HashMap<Pubkey, &Pubkey>,
program_accounts: &HashMap<Pubkey, (&Pubkey, u64)>,
loaded_programs: &LoadedProgramsForTxBatch,
) -> Vec<TransactionLoadResult> {
txs.iter()
@ -2088,13 +2098,13 @@ mod tests {
programs
.get(&account3_pubkey)
.expect("failed to find the program account"),
&&program1_pubkey
&(&program1_pubkey, 2)
);
assert_eq!(
programs
.get(&account4_pubkey)
.expect("failed to find the program account"),
&&program2_pubkey
&(&program2_pubkey, 1)
);
}
@ -2197,7 +2207,7 @@ mod tests {
programs
.get(&account3_pubkey)
.expect("failed to find the program account"),
&&program1_pubkey
&(&program1_pubkey, 1)
);
assert_eq!(lock_results[1].0, Err(TransactionError::BlockhashNotFound));
}

View File

@ -181,7 +181,7 @@ use {
sync::{
atomic::{
AtomicBool, AtomicI64, AtomicU64, AtomicUsize,
Ordering::{AcqRel, Acquire, Relaxed},
Ordering::{self, AcqRel, Acquire, Relaxed},
},
Arc, LockResult, RwLock, RwLockReadGuard, RwLockWriteGuard,
},
@ -4407,26 +4407,31 @@ impl Bank {
fn replenish_program_cache(
&self,
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
program_accounts_map: &HashMap<Pubkey, (&Pubkey, u64)>,
) -> LoadedProgramsForTxBatch {
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
let programs_and_slots: Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))> =
if self.check_program_modification_slot {
program_accounts_map
.keys()
.map(|pubkey| {
.iter()
.map(|(pubkey, (_, count))| {
(
*pubkey,
self.program_modification_slot(pubkey)
.map_or(LoadedProgramMatchCriteria::Tombstone, |slot| {
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
}),
(
self.program_modification_slot(pubkey)
.map_or(LoadedProgramMatchCriteria::Tombstone, |slot| {
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
}),
*count,
),
)
})
.collect()
} else {
program_accounts_map
.keys()
.map(|pubkey| (*pubkey, LoadedProgramMatchCriteria::NoCriteria))
.iter()
.map(|(pubkey, (_, count))| {
(*pubkey, (LoadedProgramMatchCriteria::NoCriteria, *count))
})
.collect()
};
@ -4439,7 +4444,7 @@ impl Bank {
// Load missing programs while global cache is unlocked
let missing_programs: Vec<(Pubkey, Arc<LoadedProgram>)> = missing_programs
.iter()
.map(|key| {
.map(|(key, count)| {
let program = self.load_program(key).unwrap_or_else(|err| {
// Create a tombstone for the program in the cache
debug!("Failed to load program {}, error {:?}", key, err);
@ -4448,6 +4453,7 @@ impl Bank {
LoadedProgramType::FailedVerification,
))
});
program.tx_usage_counter.store(*count, Ordering::Relaxed);
(*key, program)
})
.collect();
@ -4539,7 +4545,7 @@ impl Bank {
);
let native_loader = native_loader::id();
for builtin_program in self.builtin_programs.iter() {
program_accounts_map.insert(*builtin_program, &native_loader);
program_accounts_map.insert(*builtin_program, (&native_loader, 0));
}
let programs_loaded_for_tx_batch = Rc::new(RefCell::new(