use std::fmt;
use std::num::NonZeroUsize;
use std::collections::BTreeMap;
use log::{debug, warn};
use lru::LruCache;
use crate::{BlockIndex, BlockIndexIter};
use crate::buffer::DataBlock;
use crate::file::DirtyDataBlocks;
use super::Cache;
pub(crate) struct MemCache {
pub(crate) data_blocks_cache: LruCache<BlockIndex, DataBlock>,
pub(crate) data_blocks_dirty: BTreeMap<BlockIndex, DataBlock>, pub(crate) data_cache_blocks: usize,
pub(crate) data_block_size: usize,
}
impl fmt::Display for MemCache {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, " data cache blocks limit: {}, data lru cache size: {}, data dirty size: {}",
self.data_cache_blocks, self.data_blocks_cache.len(), self.data_blocks_dirty.len())
}
}
impl MemCache {
pub(crate) fn new(data_cache_blocks: usize, data_block_size: usize) -> Self {
Self {
data_blocks_cache: LruCache::new(
NonZeroUsize::new(data_cache_blocks).or(NonZeroUsize::new(1)).unwrap()
),
data_blocks_dirty: BTreeMap::new(),
data_cache_blocks,
data_block_size,
}
}
}
impl Cache for MemCache {
fn set_size(&self, _: usize) {
}
fn set_unlimited(&mut self) {
self.data_blocks_cache.resize(NonZeroUsize::new(usize::MAX).unwrap());
}
fn restore_limit(&mut self) {
self.data_blocks_cache.resize(
NonZeroUsize::new(self.data_cache_blocks).or(NonZeroUsize::new(1)).unwrap()
);
}
fn new_block(&self, blk_idx: BlockIndex) -> DataBlock {
DataBlock::new(blk_idx, self.data_block_size)
}
fn get(&mut self, blk_idx: &BlockIndex) -> Option<&DataBlock> {
if let Some(block) = self.data_blocks_dirty.get(blk_idx) {
debug!("Cache Hit on dirty list for block index: {}", blk_idx);
return Some(block);
}
if let Some(block) = (self.data_cache_blocks > 0).then(|| self.data_blocks_cache.get(blk_idx)).flatten() {
debug!("Cache Hit on cache list for block index: {}", blk_idx);
return Some(block);
}
None
}
fn insert(&mut self, blk_idx: BlockIndex, block: DataBlock) -> Option<DataBlock> {
let _ = self.data_blocks_cache.pop(&blk_idx);
self.data_blocks_dirty.insert(blk_idx, block)
}
fn remove(&mut self, blk_idx: &BlockIndex) -> Option<DataBlock> {
let _ = self.data_blocks_cache.pop(&blk_idx);
self.data_blocks_dirty.remove(blk_idx)
}
#[inline]
fn contains(&mut self, blk_idx: &BlockIndex) -> bool {
if self.data_blocks_dirty.contains_key(blk_idx) {
return true;
}
if let Some(block) = (self.data_cache_blocks > 0).then(|| self.data_blocks_cache.pop(blk_idx)).flatten() {
self.data_blocks_dirty.insert(*blk_idx, block);
return true;
}
false
}
fn get_mut(&mut self, blk_idx: &BlockIndex) -> Option<&mut DataBlock> {
if self.data_blocks_dirty.contains_key(blk_idx) {
return self.data_blocks_dirty.get_mut(blk_idx);
}
if let Some(block) = (self.data_cache_blocks > 0).then(|| self.data_blocks_cache.pop(blk_idx)).flatten() {
self.data_blocks_dirty.insert(*blk_idx, block);
return self.data_blocks_dirty.get_mut(blk_idx);
}
None
}
fn write_prepare(&mut self, off: usize, len: usize) -> Vec<BlockIndex> {
let mut output = Vec::new();
let blk_iter = BlockIndexIter::new(off, len, self.data_block_size);
debug!("start to write prepare for write offset {}, len {}", off, len);
for (blk_idx, start_off, data_len) in blk_iter {
if start_off == 0 && data_len == self.data_block_size {
let _ = self.data_blocks_cache.pop(&blk_idx);
continue;
}
if self.data_blocks_dirty.contains_key(&blk_idx) {
continue;
}
if let Some(block) = (self.data_cache_blocks > 0).then(|| self.data_blocks_cache.pop(&blk_idx)).flatten() {
self.data_blocks_dirty.insert(blk_idx, block);
continue;
}
output.push(blk_idx);
}
debug!("end of write prepare {} of blocks need to be retrieve", output.len());
output
}
fn update_cache(&mut self, blk_idx: &BlockIndex, off: usize, buf: &[u8]) {
if let Some(block) = self.data_blocks_dirty.get_mut(blk_idx) {
block.copy(off, buf);
} else if let Some(mut block) = self.data_blocks_cache.pop(blk_idx) {
block.copy(off, buf);
self.data_blocks_dirty.insert(*blk_idx, block);
warn!("update_cache - block index: {blk_idx} not in dirty list but in cache list, this is not by design");
} else {
let mut block = DataBlock::new(*blk_idx, self.data_block_size);
block.copy(off, buf);
self.data_blocks_dirty.insert(*blk_idx, block);
}
}
fn truncate_data_block(&mut self, blk_idx: &BlockIndex, offset_to_discard: usize) -> bool {
if let Some(block) = self.data_blocks_dirty.get_mut(&blk_idx) {
let buf = block.as_mut_slice();
let (_, to_clear) = buf.split_at_mut(offset_to_discard);
to_clear.fill(0);
debug!("data block in dirty list, data cleared");
return true;
}
if let Some(block) = (self.data_cache_blocks > 0).then(|| self.data_blocks_cache.pop(&blk_idx)).flatten() {
let buf = block.as_mut_slice();
let (_, to_clear) = buf.split_at_mut(offset_to_discard);
to_clear.fill(0);
debug!("data block in cache list, data cleared");
self.data_blocks_dirty.insert(*blk_idx, block);
return true;
}
false
}
fn dirty_count(&self) -> usize {
self.data_blocks_dirty.len()
}
fn truncate_blocks_above(&mut self, boundary: BlockIndex) -> usize {
let mut removed = 0;
let dirty: Vec<BlockIndex> = self.data_blocks_dirty
.range(boundary..)
.map(|(k, _)| *k)
.collect();
for k in dirty {
if self.data_blocks_dirty.remove(&k).is_some() {
removed += 1;
}
}
if self.data_cache_blocks > 0 {
let clean: Vec<BlockIndex> = self.data_blocks_cache
.iter()
.map(|(k, _)| *k)
.filter(|k| *k >= boundary)
.collect();
for k in clean {
let _ = self.data_blocks_cache.pop(&k);
}
}
removed
}
fn get_dirty(&self) -> DirtyDataBlocks<'_> {
let b: BTreeMap<BlockIndex, &DataBlock> = self.data_blocks_dirty.iter()
.map(|(idx, blk)| (*idx, blk))
.collect();
DirtyDataBlocks { inner: Some(b), owned: None }
}
fn clear_dirty(&mut self) {
while let Some((blk_idx, block)) = self.data_blocks_dirty.pop_first() {
if !block.is_should_cache() {
continue;
}
if self.data_cache_blocks == 0 {
continue;
}
if let Some(_) = self.data_blocks_cache.put(blk_idx, block) {
panic!("block already exists, failed to put back block index {} into data blocks cache", blk_idx);
}
}
}
fn clear_data_blocks_cache(&mut self) {
if self.data_cache_blocks > 0 {
self.data_blocks_cache.clear();
}
}
fn shutdown(&self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
fn new_cache() -> MemCache {
MemCache::new(4, 4096) }
#[test]
fn disabled_cache_does_not_panic() {
let mut c = MemCache::new(0, 4096);
assert_eq!(c.dirty_count(), 0);
assert!(c.get(&0).is_none());
assert!(!c.contains(&0));
assert!(c.get_mut(&0).is_none());
assert!(!c.truncate_data_block(&0, 100));
assert!(c.remove(&0).is_none());
assert!(c.insert(0, DataBlock::new(0, 4096)).is_none());
assert_eq!(c.dirty_count(), 1);
let v = c.write_prepare(4096 + 10, 20);
assert_eq!(v, vec![1], "block 1 is partial and not cached");
c.clear_dirty();
assert_eq!(c.dirty_count(), 0);
assert!(c.get(&0).is_none(), "a disabled cache must not retain the block");
assert_eq!(c.truncate_blocks_above(0), 0);
c.clear_data_blocks_cache();
}
#[test]
fn disabled_cache_clear_dirty_drops_should_cache_block() {
let mut c = MemCache::new(0, 4096);
let mut blk = DataBlock::new(7, 4096);
blk.set_should_cache();
blk.copy(0, &[0xAB; 4096]);
c.insert(7, blk);
assert_eq!(c.dirty_count(), 1);
c.clear_dirty();
assert_eq!(c.dirty_count(), 0);
assert!(c.get(&7).is_none(),
"with the cache disabled the block must be dropped, not cached");
}
#[test]
fn insert_and_get() {
let mut cache = new_cache();
let mut blk = DataBlock::new(0, 4096);
blk.copy(0, &[0xAA]);
cache.insert(0, blk);
let got = cache.get(&0).unwrap();
assert_eq!(got.as_slice()[0], 0xAA);
}
#[test]
fn get_missing_returns_none() {
let mut cache = new_cache();
assert!(cache.get(&99).is_none());
}
#[test]
fn insert_goes_to_dirty() {
let mut cache = new_cache();
cache.insert(0, DataBlock::new(0, 4096));
assert_eq!(cache.dirty_count(), 1);
}
#[test]
fn dirty_count_and_get_dirty() {
let mut cache = new_cache();
cache.insert(0, DataBlock::new(0, 4096));
cache.insert(5, DataBlock::new(5, 4096));
assert_eq!(cache.dirty_count(), 2);
let dirty = cache.get_dirty();
assert_eq!(dirty.len(), 2);
}
#[test]
fn clear_dirty_moves_should_cache_to_lru() {
let mut cache = new_cache();
let blk = DataBlock::new(0, 4096);
blk.set_should_cache();
cache.insert(0, blk);
assert_eq!(cache.dirty_count(), 1);
cache.clear_dirty();
assert_eq!(cache.dirty_count(), 0);
assert!(cache.get(&0).is_some());
}
#[test]
fn clear_dirty_discards_non_cacheable() {
let mut cache = new_cache();
cache.insert(0, DataBlock::new(0, 4096)); cache.clear_dirty();
assert_eq!(cache.dirty_count(), 0);
assert!(cache.get(&0).is_none());
}
#[test]
fn write_prepare_full_block_no_retrieve() {
let mut cache = new_cache();
let need = cache.write_prepare(0, 4096);
assert!(need.is_empty());
}
#[test]
fn write_prepare_partial_block_needs_retrieve() {
let mut cache = new_cache();
let need = cache.write_prepare(10, 100);
assert_eq!(need, vec![0]);
}
#[test]
fn write_prepare_partial_block_in_dirty_no_retrieve() {
let mut cache = new_cache();
cache.insert(0, DataBlock::new(0, 4096));
let need = cache.write_prepare(10, 100);
assert!(need.is_empty());
}
#[test]
fn write_prepare_cross_block() {
let mut cache = new_cache();
let need = cache.write_prepare(4090, 100);
assert_eq!(need, vec![0, 1]);
}
#[test]
fn update_cache_creates_block_if_missing() {
let mut cache = new_cache();
cache.update_cache(&0, 0, &[1, 2, 3]);
assert_eq!(cache.dirty_count(), 1);
let blk = cache.get(&0).unwrap();
assert_eq!(&blk.as_slice()[0..3], &[1, 2, 3]);
}
#[test]
fn update_cache_updates_existing_dirty() {
let mut cache = new_cache();
cache.insert(0, DataBlock::new(0, 4096));
cache.update_cache(&0, 10, &[0xFF; 4]);
let blk = cache.get(&0).unwrap();
assert_eq!(&blk.as_slice()[10..14], &[0xFF; 4]);
}
#[test]
fn truncate_dirty_block() {
let mut cache = new_cache();
let mut blk = DataBlock::new(0, 4096);
blk.copy(0, &[0xFF; 4096]);
cache.insert(0, blk);
let found = cache.truncate_data_block(&0, 100);
assert!(found);
let blk = cache.get(&0).unwrap();
assert_eq!(blk.as_slice()[99], 0xFF);
assert!(blk.as_slice()[100..].iter().all(|&b| b == 0));
}
#[test]
fn truncate_missing_block() {
let mut cache = new_cache();
assert!(!cache.truncate_data_block(&99, 0));
}
#[test]
fn truncate_blocks_above_drops_dirty() {
let mut cache = new_cache();
cache.insert(1, DataBlock::new(1, 4096));
cache.insert(9, DataBlock::new(9, 4096));
let removed = cache.truncate_blocks_above(5);
assert_eq!(removed, 1, "only the dirty block at 9 is above the boundary");
assert!(cache.get(&9).is_none(), "dirty block above boundary must be dropped");
assert!(cache.get(&1).is_some(), "block below boundary must be kept");
}
#[test]
fn truncate_blocks_above_drops_clean() {
let mut cache = new_cache();
let mut blk = DataBlock::new(9, 4096);
blk.set_should_cache();
blk.copy(0, &[0xAB; 4096]);
cache.insert(9, blk);
cache.clear_dirty();
assert_eq!(cache.dirty_count(), 0);
assert!(cache.get(&9).is_some(), "block should now be cached clean");
let removed = cache.truncate_blocks_above(5);
assert_eq!(removed, 0, "clean evictions must not be counted as dirty removals");
assert!(cache.get(&9).is_none(),
"clean cached block above the boundary must be dropped");
}
#[test]
fn truncate_blocks_above_keeps_clean_below_boundary() {
let mut cache = new_cache();
let mut blk = DataBlock::new(2, 4096);
blk.set_should_cache();
blk.copy(0, &[0xCD; 4096]);
cache.insert(2, blk);
cache.clear_dirty();
let _ = cache.truncate_blocks_above(5);
let got = cache.get(&2).expect("clean block below boundary must survive");
assert_eq!(got.as_slice()[0], 0xCD);
}
#[test]
fn truncate_blocks_above_at_boundary_is_inclusive() {
let mut cache = new_cache();
let blk = DataBlock::new(5, 4096);
blk.set_should_cache();
cache.insert(5, blk);
cache.clear_dirty();
let _ = cache.truncate_blocks_above(5);
assert!(cache.get(&5).is_none(), "boundary key itself must be dropped");
}
#[test]
fn contains_promotes_from_cache_to_dirty() {
let mut cache = new_cache();
let blk = DataBlock::new(0, 4096);
blk.set_should_cache();
cache.insert(0, blk);
cache.clear_dirty();
assert_eq!(cache.dirty_count(), 0);
assert!(cache.contains(&0));
assert_eq!(cache.dirty_count(), 1);
}
#[test]
fn get_mut_promotes_from_cache_to_dirty() {
let mut cache = new_cache();
let blk = DataBlock::new(0, 4096);
blk.set_should_cache();
cache.insert(0, blk);
cache.clear_dirty();
let blk = cache.get_mut(&0).unwrap();
blk.copy(0, &[0xBB]);
assert_eq!(cache.dirty_count(), 1);
}
#[test]
fn remove_from_dirty() {
let mut cache = new_cache();
cache.insert(0, DataBlock::new(0, 4096));
let removed = cache.remove(&0);
assert!(removed.is_some());
assert_eq!(cache.dirty_count(), 0);
}
#[test]
fn lru_eviction() {
let mut cache = MemCache::new(2, 4096); for i in 0..3 {
let blk = DataBlock::new(i, 4096);
blk.set_should_cache();
cache.insert(i, blk);
}
cache.clear_dirty();
assert!(cache.get(&0).is_none());
assert!(cache.get(&2).is_some());
}
#[test]
fn new_block_correct_size() {
let cache = new_cache();
let blk = cache.new_block(7);
assert_eq!(blk.index(), 7);
assert_eq!(blk.size(), 4096);
}
#[test]
fn set_unlimited_and_restore() {
let mut cache = MemCache::new(2, 4096);
cache.set_unlimited();
for i in 0..100 {
let blk = DataBlock::new(i, 4096);
blk.set_should_cache();
cache.insert(i, blk);
}
cache.clear_dirty();
assert!(cache.get(&0).is_some());
cache.restore_limit();
}
}