use std::{
collections::HashSet,
env::temp_dir,
fmt::{self, Debug},
path::PathBuf,
sync::atomic::AtomicU64,
};
use crate::supertable::manifest::SuperfileUri;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColdFetchMode {
#[default]
HybridWithPrefetch,
RangeOnly,
LazyForegroundWithBackgroundFill,
}
pub struct DiskCacheConfig {
pub cache_root: PathBuf,
pub disk_budget_bytes: u64,
pub cold_fetch_mode: ColdFetchMode,
pub cold_fetch_streams: usize,
pub cold_fetch_chunk_bytes: u64,
pub prefetch_concurrency: usize,
pub mmap_cold_threshold_secs: u64,
pub mmap_sweep_interval_secs: u64,
pub eviction: Box<dyn CacheEvictionPolicy>,
pub verify_crc_on_open: bool,
}
const DEFAULT_DISK_BUDGET_BYTES: u64 = 10 * (1 << 30);
const DEFAULT_COLD_FETCH_STREAMS: usize = 16;
const DEFAULT_COLD_FETCH_CHUNK_BYTES: u64 = 16 * (1 << 20);
const DEFAULT_PREFETCH_CONCURRENCY: usize = 8;
const DEFAULT_MMAP_COLD_THRESHOLD_SECS: u64 = 300;
const DEFAULT_MMAP_SWEEP_INTERVAL_SECS: u64 = 75;
impl Default for DiskCacheConfig {
fn default() -> Self {
Self {
cache_root: temp_dir().join("infino-disk-cache"),
disk_budget_bytes: DEFAULT_DISK_BUDGET_BYTES,
cold_fetch_mode: ColdFetchMode::default(),
cold_fetch_streams: DEFAULT_COLD_FETCH_STREAMS,
cold_fetch_chunk_bytes: DEFAULT_COLD_FETCH_CHUNK_BYTES,
prefetch_concurrency: DEFAULT_PREFETCH_CONCURRENCY,
mmap_cold_threshold_secs: DEFAULT_MMAP_COLD_THRESHOLD_SECS,
mmap_sweep_interval_secs: DEFAULT_MMAP_SWEEP_INTERVAL_SECS,
eviction: Box::new(LruPolicy::new()),
verify_crc_on_open: true,
}
}
}
impl Debug for DiskCacheConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DiskCacheConfig")
.field("cache_root", &self.cache_root)
.field("disk_budget_bytes", &self.disk_budget_bytes)
.field("cold_fetch_mode", &self.cold_fetch_mode)
.field("cold_fetch_streams", &self.cold_fetch_streams)
.field("cold_fetch_chunk_bytes", &self.cold_fetch_chunk_bytes)
.field("prefetch_concurrency", &self.prefetch_concurrency)
.field("mmap_cold_threshold_secs", &self.mmap_cold_threshold_secs)
.field("mmap_sweep_interval_secs", &self.mmap_sweep_interval_secs)
.field("eviction", &"<dyn CacheEvictionPolicy>")
.finish()
}
}
#[derive(Debug, Clone)]
pub struct EvictionCandidate {
pub uri: SuperfileUri,
pub size_bytes: u64,
pub last_access_us: u64,
}
pub trait CacheEvictionPolicy: Send + Sync {
fn select_for_eviction(
&self,
candidates: &[EvictionCandidate],
pinned: &HashSet<SuperfileUri>,
bytes_needed: u64,
) -> Vec<SuperfileUri>;
}
#[derive(Debug, Default)]
pub struct LruPolicy {
_seq: AtomicU64,
}
impl LruPolicy {
pub fn new() -> Self {
Self::default()
}
}
impl CacheEvictionPolicy for LruPolicy {
fn select_for_eviction(
&self,
candidates: &[EvictionCandidate],
pinned: &HashSet<SuperfileUri>,
bytes_needed: u64,
) -> Vec<SuperfileUri> {
let mut eligible: Vec<&EvictionCandidate> = candidates
.iter()
.filter(|c| !pinned.contains(&c.uri))
.collect();
eligible.sort_by_key(|c| c.last_access_us);
let mut victims = Vec::new();
let mut freed = 0u64;
for c in eligible {
if freed >= bytes_needed {
break;
}
victims.push(c.uri);
freed = freed.saturating_add(c.size_bytes);
}
if freed < bytes_needed {
Vec::new()
} else {
victims
}
}
}