use schnellru::{Limiter, LruMap};
use sp_runtime::{traits::Block as BlockT, Justifications};
const LOG_TARGET: &str = "db::pin";
const PINNING_CACHE_SIZE: usize = 2048;
struct PinnedBlockCacheEntry<Block: BlockT> {
ref_count: u32,
pub justifications: Option<Option<Justifications>>,
pub body: Option<Option<Vec<Block::Extrinsic>>>,
}
impl<Block: BlockT> Default for PinnedBlockCacheEntry<Block> {
fn default() -> Self {
Self { ref_count: 0, justifications: None, body: None }
}
}
impl<Block: BlockT> PinnedBlockCacheEntry<Block> {
pub fn decrease_ref(&mut self) {
self.ref_count = self.ref_count.saturating_sub(1);
}
pub fn increase_ref(&mut self) {
self.ref_count = self.ref_count.saturating_add(1);
}
pub fn has_no_references(&self) -> bool {
self.ref_count == 0
}
}
#[derive(Copy, Clone, Debug)]
struct LoggingByLengthLimiter {
max_length: usize,
}
impl LoggingByLengthLimiter {
pub const fn new(max_length: usize) -> LoggingByLengthLimiter {
LoggingByLengthLimiter { max_length }
}
}
impl<Block: BlockT> Limiter<Block::Hash, PinnedBlockCacheEntry<Block>> for LoggingByLengthLimiter {
type KeyToInsert<'a> = Block::Hash;
type LinkType = usize;
fn is_over_the_limit(&self, length: usize) -> bool {
length > self.max_length
}
fn on_insert(
&mut self,
_length: usize,
key: Self::KeyToInsert<'_>,
value: PinnedBlockCacheEntry<Block>,
) -> Option<(Block::Hash, PinnedBlockCacheEntry<Block>)> {
if self.max_length > 0 {
Some((key, value))
} else {
None
}
}
fn on_replace(
&mut self,
_length: usize,
_old_key: &mut Block::Hash,
_new_key: Block::Hash,
_old_value: &mut PinnedBlockCacheEntry<Block>,
_new_value: &mut PinnedBlockCacheEntry<Block>,
) -> bool {
true
}
fn on_removed(&mut self, key: &mut Block::Hash, value: &mut PinnedBlockCacheEntry<Block>) {
if value.ref_count > 0 {
log::warn!(
target: LOG_TARGET,
"Pinned block cache limit reached. Evicting value. hash = {}",
key
);
} else {
log::trace!(
target: LOG_TARGET,
"Evicting value from pinned block cache. hash = {}",
key
)
}
}
fn on_cleared(&mut self) {}
fn on_grow(&mut self, _new_memory_usage: usize) -> bool {
true
}
}
pub struct PinnedBlocksCache<Block: BlockT> {
cache: LruMap<Block::Hash, PinnedBlockCacheEntry<Block>, LoggingByLengthLimiter>,
}
impl<Block: BlockT> PinnedBlocksCache<Block> {
pub fn new() -> Self {
Self { cache: LruMap::new(LoggingByLengthLimiter::new(PINNING_CACHE_SIZE)) }
}
pub fn pin(&mut self, hash: Block::Hash) {
match self.cache.get_or_insert(hash, Default::default) {
Some(entry) => {
entry.increase_ref();
log::trace!(
target: LOG_TARGET,
"Bumped cache refcount. hash = {}, num_entries = {}",
hash,
self.cache.len()
);
},
None => {
log::warn!(target: LOG_TARGET, "Unable to bump reference count. hash = {}", hash)
},
};
}
pub fn clear(&mut self) {
self.cache.clear();
}
pub fn contains(&self, hash: Block::Hash) -> bool {
self.cache.peek(&hash).is_some()
}
pub fn insert_body(&mut self, hash: Block::Hash, extrinsics: Option<Vec<Block::Extrinsic>>) {
match self.cache.peek_mut(&hash) {
Some(entry) => {
entry.body = Some(extrinsics);
log::trace!(
target: LOG_TARGET,
"Cached body. hash = {}, num_entries = {}",
hash,
self.cache.len()
);
},
None => log::warn!(
target: LOG_TARGET,
"Unable to insert body for uncached item. hash = {}",
hash
),
}
}
pub fn insert_justifications(
&mut self,
hash: Block::Hash,
justifications: Option<Justifications>,
) {
match self.cache.peek_mut(&hash) {
Some(entry) => {
entry.justifications = Some(justifications);
log::trace!(
target: LOG_TARGET,
"Cached justification. hash = {}, num_entries = {}",
hash,
self.cache.len()
);
},
None => log::warn!(
target: LOG_TARGET,
"Unable to insert justifications for uncached item. hash = {}",
hash
),
}
}
pub fn unpin(&mut self, hash: Block::Hash) {
if let Some(entry) = self.cache.peek_mut(&hash) {
entry.decrease_ref();
if entry.has_no_references() {
self.cache.remove(&hash);
}
}
}
pub fn justifications(&self, hash: &Block::Hash) -> Option<&Option<Justifications>> {
self.cache.peek(hash).and_then(|entry| entry.justifications.as_ref())
}
pub fn body(&self, hash: &Block::Hash) -> Option<&Option<Vec<Block::Extrinsic>>> {
self.cache.peek(hash).and_then(|entry| entry.body.as_ref())
}
}