mod metrics;
mod options;
mod page_id;
pub mod allocator;
pub mod caching_reader;
pub mod evictor;
pub mod manager;
pub mod store;
pub use allocator::{Allocator, HashAllocator};
pub use caching_reader::{read_through_cache, ExternalRangeReader, FillMode};
pub use manager::LocalCacheManager;
pub use metrics::name as metric_name;
pub use options::CacheManagerOptions;
pub use page_id::{CacheScope, PageId, PageInfo};
use bytes::Bytes;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct PageReadRequest {
pub page_id: PageId,
pub page_offset: usize,
pub len: usize,
}
#[inline]
pub(crate) fn page_cache_eligible(file_id: i64) -> bool {
file_id > 0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheState {
NotInUse,
ReadOnly,
ReadWrite,
}
impl CacheState {
pub fn as_i64(self) -> i64 {
match self {
CacheState::NotInUse => 0,
CacheState::ReadOnly => 1,
CacheState::ReadWrite => 2,
}
}
}
#[async_trait::async_trait]
pub trait CacheManager: Send + Sync {
async fn put(&self, page_id: &PageId, page: Bytes) -> bool;
fn schedule_fill(self: Arc<Self>, page_id: PageId, page: Bytes)
where
Self: 'static,
{
tokio::spawn(async move {
let _ = self.put(&page_id, page).await;
});
}
async fn get(&self, page_id: &PageId, page_offset: usize, dst: &mut [u8]) -> usize;
async fn get_bytes(&self, page_id: &PageId, page_offset: usize, len: usize) -> Bytes {
if len == 0 {
return Bytes::new();
}
let mut dst = vec![0u8; len];
let n = self.get(page_id, page_offset, &mut dst).await;
if n == 0 {
Bytes::new()
} else {
dst.truncate(n);
Bytes::from(dst)
}
}
async fn get_batch_bytes(&self, requests: &[PageReadRequest]) -> Vec<Bytes> {
let mut out = Vec::with_capacity(requests.len());
for req in requests {
out.push(self.get_bytes(&req.page_id, req.page_offset, req.len).await);
}
out
}
async fn delete(&self, page_id: &PageId) -> bool;
async fn invalidate(&self, file_id: &str);
async fn on_file_open(&self, _file_id: &str, _length: i64, _last_modification_time_ms: i64) {}
fn state(&self) -> CacheState;
}
#[derive(Debug, Default, Clone)]
pub struct DisabledCacheManager;
#[async_trait::async_trait]
impl CacheManager for DisabledCacheManager {
async fn put(&self, _page_id: &PageId, _page: Bytes) -> bool {
false
}
fn schedule_fill(self: Arc<Self>, _page_id: PageId, _page: Bytes) {
}
async fn get(&self, _page_id: &PageId, _page_offset: usize, _dst: &mut [u8]) -> usize {
0
}
async fn delete(&self, _page_id: &PageId) -> bool {
false
}
async fn invalidate(&self, _file_id: &str) {}
fn state(&self) -> CacheState {
CacheState::NotInUse
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_state_encoding() {
assert_eq!(CacheState::NotInUse.as_i64(), 0);
assert_eq!(CacheState::ReadOnly.as_i64(), 1);
assert_eq!(CacheState::ReadWrite.as_i64(), 2);
}
#[tokio::test]
async fn disabled_manager_always_misses() {
let mgr = DisabledCacheManager;
let id = PageId::new("file-1", 0);
assert!(!mgr.put(&id, Bytes::from_static(b"hello")).await);
let mut dst = [0u8; 8];
assert_eq!(mgr.get(&id, 0, &mut dst).await, 0);
assert_eq!(dst, [0u8; 8]);
assert!(!mgr.delete(&id).await);
mgr.invalidate("file-1").await; assert_eq!(mgr.state(), CacheState::NotInUse);
}
#[test]
fn page_cache_eligible_requires_positive_file_id() {
assert!(!page_cache_eligible(0), "file_id=0 must disable cache");
assert!(
!page_cache_eligible(-1),
"negative file_id must disable cache"
);
assert!(page_cache_eligible(1));
assert!(page_cache_eligible(i64::MAX));
}
}