use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BlockKey {
pub sstable_id: u64,
pub block_offset: u64,
}
impl BlockKey {
pub fn new(sstable_id: u64, block_offset: u64) -> Self {
Self {
sstable_id,
block_offset,
}
}
}
pub type Block = Arc<[u8]>;
pub trait BlockCache: Send + Sync {
fn get(&self, key: &BlockKey) -> Option<Block>;
fn put(&self, key: BlockKey, block: Block);
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn clear(&self);
}
pub struct LruBlockCache {
inner: std::sync::Mutex<LruInner>,
}
struct LruInner {
capacity: usize,
map: HashMap<BlockKey, usize>,
nodes: Vec<Node>,
head: Option<usize>,
tail: Option<usize>,
free: Vec<usize>,
hits: u64,
misses: u64,
}
struct Node {
key: BlockKey,
block: Block,
prev: Option<usize>,
next: Option<usize>,
}
impl LruBlockCache {
pub fn new(capacity: usize) -> Self {
let cap = capacity.max(1);
Self {
inner: std::sync::Mutex::new(LruInner {
capacity: cap,
map: HashMap::with_capacity(cap),
nodes: Vec::with_capacity(cap),
head: None,
tail: None,
free: Vec::new(),
hits: 0,
misses: 0,
}),
}
}
pub fn capacity(&self) -> usize {
self.inner.lock().unwrap().capacity
}
pub fn hits(&self) -> u64 {
self.inner.lock().unwrap().hits
}
pub fn misses(&self) -> u64 {
self.inner.lock().unwrap().misses
}
}
impl BlockCache for LruBlockCache {
fn get(&self, key: &BlockKey) -> Option<Block> {
let mut g = self.inner.lock().unwrap();
match g.map.get(key).copied() {
Some(idx) => {
let block = g.nodes[idx].block.clone();
g.move_to_front(idx);
g.hits += 1;
Some(block)
}
None => {
g.misses += 1;
None
}
}
}
fn put(&self, key: BlockKey, block: Block) {
let mut g = self.inner.lock().unwrap();
if let Some(&idx) = g.map.get(&key) {
g.nodes[idx].block = block;
g.move_to_front(idx);
return;
}
if g.map.len() >= g.capacity {
if let Some(tail_idx) = g.tail {
let tail_key = g.nodes[tail_idx].key;
g.detach(tail_idx);
g.map.remove(&tail_key);
g.free.push(tail_idx);
}
}
let idx = if let Some(slot) = g.free.pop() {
g.nodes[slot] = Node {
key,
block,
prev: None,
next: None,
};
slot
} else {
g.nodes.push(Node {
key,
block,
prev: None,
next: None,
});
g.nodes.len() - 1
};
g.map.insert(key, idx);
g.push_front(idx);
}
fn len(&self) -> usize {
self.inner.lock().unwrap().map.len()
}
fn clear(&self) {
let mut g = self.inner.lock().unwrap();
g.map.clear();
g.nodes.clear();
g.free.clear();
g.head = None;
g.tail = None;
}
}
impl LruInner {
fn push_front(&mut self, idx: usize) {
self.nodes[idx].prev = None;
self.nodes[idx].next = self.head;
if let Some(h) = self.head {
self.nodes[h].prev = Some(idx);
}
self.head = Some(idx);
if self.tail.is_none() {
self.tail = Some(idx);
}
}
fn detach(&mut self, idx: usize) {
let prev = self.nodes[idx].prev;
let next = self.nodes[idx].next;
if let Some(p) = prev {
self.nodes[p].next = next;
} else {
self.head = next;
}
if let Some(n) = next {
self.nodes[n].prev = prev;
} else {
self.tail = prev;
}
self.nodes[idx].prev = None;
self.nodes[idx].next = None;
}
fn move_to_front(&mut self, idx: usize) {
if self.head == Some(idx) {
return;
}
self.detach(idx);
self.push_front(idx);
}
}
#[cfg(test)]
#[path = "block_cache_integration_tests.rs"]
mod tests;