use crate::engine::error::VfsResult;
use crate::engine::metadata::MetadataStore;
use crate::engine::types::{
BlockKey, ChunkEdit, ChunkRange, ChunkRef, CreateInodeAttrs, DentryStat, InodeMeta, InodePatch,
SnapshotId,
};
use async_trait::async_trait;
use std::collections::{BTreeMap, VecDeque};
use std::sync::Mutex;
pub struct CachedMetadataStore<M> {
inner: M,
cache: Mutex<CacheState>,
}
#[derive(Debug)]
struct CacheState {
capacity: usize,
generation: u64,
resolve: BTreeMap<String, Option<InodeMeta>>,
lstat: BTreeMap<String, Option<InodeMeta>>,
list_dir: BTreeMap<u64, Vec<DentryStat>>,
order: VecDeque<CacheKey>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum CacheKey {
Resolve(String),
Lstat(String),
ListDir(u64),
}
impl<M> CachedMetadataStore<M> {
pub fn new(inner: M, capacity: usize) -> Self {
Self {
inner,
cache: Mutex::new(CacheState {
capacity,
generation: 0,
resolve: BTreeMap::new(),
lstat: BTreeMap::new(),
list_dir: BTreeMap::new(),
order: VecDeque::new(),
}),
}
}
pub fn into_inner(self) -> M {
self.inner
}
fn clear_after_mutation(&self) {
let mut cache = self.cache.lock().expect("cache mutex poisoned");
cache.generation = cache.generation.wrapping_add(1);
cache.resolve.clear();
cache.lstat.clear();
cache.list_dir.clear();
cache.order.clear();
}
}
impl CacheState {
fn remember(&mut self, key: CacheKey) {
if self.capacity == 0 {
self.resolve.clear();
self.lstat.clear();
self.list_dir.clear();
self.order.clear();
return;
}
self.order.push_back(key);
while self.order.len() > self.capacity {
if let Some(evicted) = self.order.pop_front() {
match evicted {
CacheKey::Resolve(path) => {
self.resolve.remove(&path);
}
CacheKey::Lstat(path) => {
self.lstat.remove(&path);
}
CacheKey::ListDir(ino) => {
self.list_dir.remove(&ino);
}
}
}
}
}
}
#[async_trait]
impl<M: MetadataStore> MetadataStore for CachedMetadataStore<M> {
async fn resolve(&self, path: &str) -> VfsResult<InodeMeta> {
let generation = {
let cache = self.cache.lock().expect("cache mutex poisoned");
if let Some(cached) = cache.resolve.get(path).cloned() {
return cached.ok_or_else(|| crate::engine::error::VfsError::enoent(path));
}
cache.generation
};
let result = self.inner.resolve(path).await;
let mut cache = self.cache.lock().expect("cache mutex poisoned");
if cache.generation == generation {
cache.resolve.insert(path.to_string(), result.clone().ok());
cache.remember(CacheKey::Resolve(path.to_string()));
}
result
}
async fn resolve_parent(&self, path: &str) -> VfsResult<(InodeMeta, String)> {
self.inner.resolve_parent(path).await
}
async fn lstat(&self, path: &str) -> VfsResult<InodeMeta> {
let generation = {
let cache = self.cache.lock().expect("cache mutex poisoned");
if let Some(cached) = cache.lstat.get(path).cloned() {
return cached.ok_or_else(|| crate::engine::error::VfsError::enoent(path));
}
cache.generation
};
let result = self.inner.lstat(path).await;
let mut cache = self.cache.lock().expect("cache mutex poisoned");
if cache.generation == generation {
cache.lstat.insert(path.to_string(), result.clone().ok());
cache.remember(CacheKey::Lstat(path.to_string()));
}
result
}
async fn list_dir(&self, ino: u64) -> VfsResult<Vec<DentryStat>> {
let generation = {
let cache = self.cache.lock().expect("cache mutex poisoned");
if let Some(cached) = cache.list_dir.get(&ino).cloned() {
return Ok(cached);
}
cache.generation
};
let entries = self.inner.list_dir(ino).await?;
let mut cache = self.cache.lock().expect("cache mutex poisoned");
if cache.generation == generation {
cache.list_dir.insert(ino, entries.clone());
cache.remember(CacheKey::ListDir(ino));
}
Ok(entries)
}
async fn create(
&self,
parent: u64,
name: &str,
attrs: CreateInodeAttrs,
) -> VfsResult<InodeMeta> {
let result = self.inner.create(parent, name, attrs).await;
self.clear_after_mutation();
result
}
async fn link(&self, parent: u64, name: &str, target: u64) -> VfsResult<()> {
let result = self.inner.link(parent, name, target).await;
self.clear_after_mutation();
result
}
async fn remove(&self, parent: u64, name: &str) -> VfsResult<Vec<BlockKey>> {
let result = self.inner.remove(parent, name).await;
self.clear_after_mutation();
result
}
async fn rename(
&self,
src_parent: u64,
src: &str,
dst_parent: u64,
dst: &str,
) -> VfsResult<Vec<BlockKey>> {
let result = self.inner.rename(src_parent, src, dst_parent, dst).await;
self.clear_after_mutation();
result
}
async fn set_attr(&self, ino: u64, patch: InodePatch) -> VfsResult<Vec<BlockKey>> {
let result = self.inner.set_attr(ino, patch).await;
self.clear_after_mutation();
result
}
async fn commit_write(
&self,
ino: u64,
edits: Vec<ChunkEdit>,
new_size: u64,
) -> VfsResult<Vec<BlockKey>> {
let result = self.inner.commit_write(ino, edits, new_size).await;
self.clear_after_mutation();
result
}
async fn get_chunks(&self, ino: u64, range: ChunkRange) -> VfsResult<Vec<ChunkRef>> {
self.inner.get_chunks(ino, range).await
}
async fn snapshot(&self, root: u64) -> VfsResult<SnapshotId> {
self.inner.snapshot(root).await
}
async fn fork(&self, snap: SnapshotId) -> VfsResult<u64> {
self.inner.fork(snap).await
}
async fn gc(&self) -> VfsResult<Vec<BlockKey>> {
self.inner.gc().await
}
}