use std::io::{Error, ErrorKind, Result};
use std::sync::atomic::Ordering;
use std::path::Path;
use serde::{Deserialize, Serialize};
use foyer::{
HybridCacheBuilder, HybridCache,
BlockEngineBuilder, FsDeviceBuilder, DeviceBuilder,
HybridCachePolicy, HybridCacheProperties, Location,
};
use btree_ondisk::{NodeCache, NodeTieredCacheStats};
use super::config::HyperFileNodeCacheConfig;
#[derive(Clone)]
pub struct LocalDiskNodeCache {
hybrid: Option<HybridCache<u64, Vec<u8>>>,
stats: NodeTieredCacheStats,
}
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub enum LocalDiskNodeCacheOpenMode {
#[default]
Disabled,
ReuseCache,
ReuseSpace,
Recreate,
}
impl Drop for LocalDiskNodeCache {
fn drop(&mut self) {
futures::executor::block_on(async {
self.close().await
});
}
}
impl<P: Send + Copy + Into<u64>> NodeCache<P> for LocalDiskNodeCache {
fn push(&self, p: &P, data: &[u8]) {
let key = (*p).into();
self.stats.total_push.fetch_add(1, Ordering::SeqCst);
let Some(ref hybrid) = self.hybrid else {
return;
};
hybrid.insert_with_properties(key, data.to_vec(),
HybridCacheProperties::default()
.with_ephemeral(false)
.with_location(Location::OnDisk)
);
}
async fn load(&self, p: P, data: &mut [u8]) -> Result<bool> {
let key = p.into();
self.stats.total_load.fetch_add(1, Ordering::SeqCst);
let Some(ref hybrid) = self.hybrid else {
return Ok(false);
};
match hybrid.get(&key).await
.map_err(|e| {
let err_msg = format!("{}", e);
Error::new(ErrorKind::Other, err_msg)
})?
{
Some(entry) => {
self.stats.total_hit.fetch_add(1, Ordering::SeqCst);
data.copy_from_slice(&entry.value());
Ok(true)
},
None => {
Ok(false)
},
}
}
fn invalid(&self, p: &P) {
let key = (*p).into();
self.stats.total_remove.fetch_add(1, Ordering::SeqCst);
let Some(ref hybrid) = self.hybrid else {
return;
};
hybrid.remove(&key);
}
fn evict(&self) {
let Some(ref hybrid) = self.hybrid else {
return;
};
hybrid.memory().evict_all();
}
fn get_stats(&self) -> NodeTieredCacheStats {
self.stats.clone()
}
fn shutdown(&self) {
futures::executor::block_on(async {
self.close().await
});
}
}
impl LocalDiskNodeCache {
pub async fn from(config: &HyperFileNodeCacheConfig) -> Self {
let HyperFileNodeCacheConfig::LocalDisk(c) = config;
if c.mode == LocalDiskNodeCacheOpenMode::Disabled {
return Self::new_none();
}
Self::new(&c.dir, c.capacity, c.mode.clone()).await
}
pub fn new_none() -> Self {
Self {
hybrid: None,
stats: NodeTieredCacheStats::default(),
}
}
pub async fn new(dir: impl AsRef<Path>, capacity: usize, mode: LocalDiskNodeCacheOpenMode) -> Self {
if mode == LocalDiskNodeCacheOpenMode::Recreate {
let _ = tokio::fs::remove_dir_all(&dir).await;
}
let cache_device = FsDeviceBuilder::new(dir)
.with_capacity(capacity)
.build()
.expect("failed to build cache device");
let hybrid: HybridCache<u64, Vec<u8>> = HybridCacheBuilder::new()
.with_name("btncache")
.with_policy(HybridCachePolicy::WriteOnInsertion)
.memory(0)
.storage()
.with_engine_config(BlockEngineBuilder::new(cache_device))
.build()
.await
.expect("failed to build cache");
if mode == LocalDiskNodeCacheOpenMode::ReuseSpace {
let _ = hybrid.storage().destroy().await.expect("failed to clear cache");
let _ = hybrid.storage().wait().await;
}
Self {
hybrid: Some(hybrid),
stats: NodeTieredCacheStats::default(),
}
}
pub async fn close(&self) {
let Some(ref hybrid) = self.hybrid else {
return;
};
hybrid.memory().flush().await;
hybrid.close().await.expect("failed to close hybrid");
}
}