use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use noxu_log::file_manager::FileManager;
pub struct DiskLimitTracker {
max_disk: u64,
free_disk: u64,
reserved_disk: u64,
file_manager: Option<Arc<FileManager>>,
violated: AtomicBool,
available_log_size: AtomicU64,
total_log_size: AtomicU64,
disk_free_space: AtomicU64,
}
impl DiskLimitTracker {
pub fn new(
max_disk: u64,
free_disk: u64,
reserved_disk: u64,
file_manager: Option<Arc<FileManager>>,
) -> Self {
DiskLimitTracker {
max_disk,
free_disk,
reserved_disk,
file_manager,
violated: AtomicBool::new(false),
available_log_size: AtomicU64::new(0),
total_log_size: AtomicU64::new(0),
disk_free_space: AtomicU64::new(0),
}
}
#[inline]
pub fn is_enabled(&self) -> bool {
self.max_disk > 0 || self.free_disk > 0 || self.reserved_disk > 0
}
#[inline]
pub fn is_violated(&self) -> bool {
self.is_enabled() && self.violated.load(Ordering::Relaxed)
}
pub fn last_total_log_size(&self) -> u64 {
self.total_log_size.load(Ordering::Relaxed)
}
pub fn effective_limit(&self) -> u64 {
if self.max_disk > 0 { self.max_disk } else { self.free_disk }
}
pub fn refresh(&self) {
if !self.is_enabled() {
return;
}
let Some(fm) = self.file_manager.as_ref() else {
return;
};
let total_size = match fm.total_log_size() {
Ok(v) => v,
Err(_) => return,
};
let disk_free = match fm.disk_free_space() {
Ok(v) => v,
Err(_) => return,
};
self.recalc(total_size, disk_free);
}
pub fn recalc(&self, total_size: u64, disk_free_space: u64) {
let free_reserve = self.free_disk as i64 + self.reserved_disk as i64;
let free_bytes1: i64 = disk_free_space as i64 - free_reserve;
let avail_bytes: i64 = if self.max_disk > 0 {
let max_room = self.max_disk as i64 - total_size as i64;
free_bytes1.min(max_room)
} else {
free_bytes1
};
let violated = avail_bytes <= 0;
self.total_log_size.store(total_size, Ordering::Relaxed);
self.disk_free_space.store(disk_free_space, Ordering::Relaxed);
self.available_log_size.store(avail_bytes as u64, Ordering::Relaxed);
self.violated.store(violated, Ordering::Release);
}
pub fn violation_message(&self) -> String {
format!(
"Disk usage is not within maxDisk or freeDisk limits and write \
operations are prohibited: maxDisk={}, freeDisk={}, \
reservedDisk={}, totalLogSize={}, diskFreeSpace={}, \
availableLogSize={}",
self.max_disk,
self.free_disk,
self.reserved_disk,
self.total_log_size.load(Ordering::Relaxed),
self.disk_free_space.load(Ordering::Relaxed),
self.available_log_size.load(Ordering::Relaxed) as i64,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disabled_never_violates() {
let t = DiskLimitTracker::new(0, 0, 0, None);
assert!(!t.is_enabled());
t.recalc(1_000_000, 0); assert!(!t.is_violated());
}
#[test]
fn max_disk_cap() {
let t = DiskLimitTracker::new(100, 0, 0, None);
t.recalc(50, 1_000_000);
assert!(!t.is_violated(), "50 < 100 cap, ok");
t.recalc(100, 1_000_000);
assert!(t.is_violated(), "100 == cap -> availBytes 0 -> violated");
t.recalc(150, 1_000_000);
assert!(t.is_violated(), "over cap");
t.recalc(50, 1_000_000);
assert!(!t.is_violated(), "back under cap -> resume");
}
#[test]
fn free_disk_reserve() {
let t = DiskLimitTracker::new(0, 25, 0, None);
t.recalc(75, 20); assert!(t.is_violated());
t.recalc(75, 30); assert!(!t.is_violated());
}
#[test]
fn reserved_disk_adds_to_free_reserve() {
let t = DiskLimitTracker::new(0, 25, 10, None);
assert!(t.is_enabled());
t.recalc(0, 30); assert!(t.is_violated(), "30 free < 25+10 reserve -> violated");
t.recalc(0, 40); assert!(!t.is_violated(), "40 free > 25+10 reserve -> ok");
let t2 = DiskLimitTracker::new(0, 0, 10, None);
assert!(t2.is_enabled());
t2.recalc(0, 5); assert!(t2.is_violated());
t2.recalc(0, 20); assert!(!t2.is_violated());
}
#[test]
fn disabled_refresh_is_noop_no_probe() {
let t = DiskLimitTracker::new(0, 0, 0, None);
t.refresh(); assert!(!t.is_violated());
t.violated.store(true, Ordering::Relaxed);
assert!(!t.is_violated(), "disabled tracker never reports a violation");
}
#[test]
fn both_limits_min_governs() {
let t = DiskLimitTracker::new(80, 25, 0, None);
t.recalc(50, 20);
assert!(t.is_violated());
let t2 = DiskLimitTracker::new(80, 5, 0, None);
t2.recalc(50, 20);
assert!(!t2.is_violated());
}
}