use log::{info, trace};
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use sysinfo::System;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PreloadStrategy {
Spiral,
Forward,
}
#[derive(Debug)]
pub struct CacheManager {
memory_usage: Arc<AtomicUsize>,
max_memory_bytes: AtomicUsize,
current_epoch: Arc<AtomicU64>,
dirty_repaint: Arc<AtomicBool>,
}
impl CacheManager {
pub fn new(mem_fraction: f64, reserve_gb: f64) -> Self {
let mut sys = System::new_all();
sys.refresh_memory();
let available = sys.available_memory() as usize;
let reserve = (reserve_gb * 1024.0 * 1024.0 * 1024.0) as usize;
let usable = available.saturating_sub(reserve);
let max_memory_bytes = (usable as f64 * mem_fraction) as usize;
info!(
"CacheManager init: available={} MB, reserve={} MB, limit={} MB ({}%)",
available / 1024 / 1024,
reserve / 1024 / 1024,
max_memory_bytes / 1024 / 1024,
(mem_fraction * 100.0) as u32
);
Self {
memory_usage: Arc::new(AtomicUsize::new(0)),
max_memory_bytes: AtomicUsize::new(max_memory_bytes),
current_epoch: Arc::new(AtomicU64::new(0)),
dirty_repaint: Arc::new(AtomicBool::new(false)),
}
}
pub fn increment_epoch(&self) -> u64 {
let new_epoch = self.current_epoch.fetch_add(1, Ordering::Relaxed) + 1;
trace!("Epoch incremented: {}", new_epoch);
new_epoch
}
pub fn current_epoch(&self) -> u64 {
self.current_epoch.load(Ordering::Relaxed)
}
pub fn epoch_ref(&self) -> Arc<AtomicU64> {
Arc::clone(&self.current_epoch)
}
pub fn mark_dirty(&self) {
self.dirty_repaint.store(true, Ordering::Relaxed);
}
pub fn take_dirty(&self) -> bool {
self.dirty_repaint.swap(false, Ordering::Relaxed)
}
pub fn check_memory_limit(&self) -> bool {
self.memory_usage.load(Ordering::Acquire) > self.max_memory_bytes.load(Ordering::Acquire)
}
pub fn mem(&self) -> (usize, usize) {
let usage = self.memory_usage.load(Ordering::Relaxed);
let limit = self.max_memory_bytes.load(Ordering::Relaxed);
(usage, limit)
}
pub fn mem_usage_fraction(&self) -> f64 {
let (usage, limit) = self.mem();
if limit == 0 {
0.0
} else {
usage as f64 / limit as f64
}
}
pub fn add_memory(&self, bytes: usize) {
let new_usage = self.memory_usage.fetch_add(bytes, Ordering::Relaxed) + bytes;
let limit = self.max_memory_bytes.load(Ordering::Relaxed);
if new_usage > limit {
trace!(
"Memory limit exceeded: {} MB / {} MB",
new_usage / 1024 / 1024,
limit / 1024 / 1024
);
}
}
pub fn free_memory(&self, bytes: usize) {
loop {
let current = self.memory_usage.load(Ordering::Acquire);
let new_val = current.saturating_sub(bytes);
if self
.memory_usage
.compare_exchange_weak(current, new_val, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
break;
}
}
}
pub fn set_memory_limit(&self, mem_fraction: f64, reserve_gb: f64) {
let mut sys = System::new_all();
sys.refresh_memory();
let available = sys.available_memory() as usize;
let reserve = (reserve_gb * 1024.0 * 1024.0 * 1024.0) as usize;
let usable = available.saturating_sub(reserve);
let new_limit = (usable as f64 * mem_fraction) as usize;
self.max_memory_bytes.store(new_limit, Ordering::Relaxed);
info!(
"Memory limit updated: {} MB ({}%)",
new_limit / 1024 / 1024,
(mem_fraction * 100.0) as u32
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_manager_creation() {
let manager = CacheManager::new(0.5, 1.0);
assert_eq!(manager.current_epoch(), 0);
let (usage, _limit) = manager.mem();
assert_eq!(usage, 0);
}
#[test]
fn test_epoch_increment() {
let manager = CacheManager::new(0.5, 1.0);
assert_eq!(manager.current_epoch(), 0);
let epoch1 = manager.increment_epoch();
assert_eq!(epoch1, 1);
assert_eq!(manager.current_epoch(), 1);
let epoch2 = manager.increment_epoch();
assert_eq!(epoch2, 2);
}
#[test]
fn test_memory_tracking() {
let manager = CacheManager::new(0.5, 1.0);
manager.add_memory(1024 * 1024); let (usage, _) = manager.mem();
assert_eq!(usage, 1024 * 1024);
manager.free_memory(512 * 1024); let (usage, _) = manager.mem();
assert_eq!(usage, 512 * 1024);
}
}