use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
const FALLBACK_BUDGET_PER_CORE: u64 = 128 * 1024 * 1024;
const SLAB_MEMORY_FRACTION: f64 = 0.10;
const MIN_BUDGET_PER_CORE: u64 = 32 * 1024 * 1024;
const MAX_BUDGET_PER_CORE: u64 = 1024 * 1024 * 1024;
pub struct ConsumerSlabAccount {
pinned_bytes: AtomicU64,
shed: AtomicBool,
core_id: usize,
}
impl ConsumerSlabAccount {
pub fn new(core_id: usize) -> Self {
Self {
pinned_bytes: AtomicU64::new(0),
shed: AtomicBool::new(false),
core_id,
}
}
pub fn add_pinned(&self, bytes: u64) {
self.pinned_bytes.fetch_add(bytes, Ordering::Relaxed);
}
pub fn release_pinned(&self, bytes: u64) {
let current = self.pinned_bytes.load(Ordering::Relaxed);
debug_assert!(
bytes <= current,
"slab over-release: releasing {bytes} but only {current} pinned (core {})",
self.core_id,
);
self.pinned_bytes
.fetch_sub(bytes.min(current), Ordering::Relaxed);
}
pub fn reset(&self) {
self.pinned_bytes.store(0, Ordering::Relaxed);
}
pub fn pinned_bytes(&self) -> u64 {
self.pinned_bytes.load(Ordering::Relaxed)
}
pub fn is_shed(&self) -> bool {
self.shed.load(Ordering::Relaxed)
}
pub fn mark_shed(&self) {
self.shed.store(true, Ordering::Relaxed);
}
pub fn clear_shed(&self) {
self.shed.store(false, Ordering::Relaxed);
}
pub fn core_id(&self) -> usize {
self.core_id
}
}
pub struct SlabBudget {
limit: u64,
total_sheds: AtomicU64,
}
impl SlabBudget {
pub fn new() -> Self {
Self {
limit: FALLBACK_BUDGET_PER_CORE,
total_sheds: AtomicU64::new(0),
}
}
pub fn for_cores(num_cores: usize) -> Self {
let cores = num_cores.max(1) as u64;
let limit = match detect_system_memory_bytes() {
Some(total_mem) => {
let total_slab = (total_mem as f64 * SLAB_MEMORY_FRACTION) as u64;
let per_core = total_slab / cores;
per_core.clamp(MIN_BUDGET_PER_CORE, MAX_BUDGET_PER_CORE)
}
None => FALLBACK_BUDGET_PER_CORE,
};
tracing::info!(
limit_mb = limit / (1024 * 1024),
num_cores,
"slab budget auto-tuned from system memory"
);
Self {
limit,
total_sheds: AtomicU64::new(0),
}
}
pub fn with_limit(limit: u64) -> Self {
Self {
limit,
total_sheds: AtomicU64::new(0),
}
}
pub fn check_and_shed(&self, accounts: &[&ConsumerSlabAccount]) -> Option<usize> {
let total: u64 = accounts.iter().map(|a| a.pinned_bytes()).sum();
if total <= self.limit {
return None;
}
let slowest = accounts
.iter()
.filter(|a| !a.is_shed()) .max_by_key(|a| a.pinned_bytes())?;
if slowest.pinned_bytes() == 0 {
return None; }
slowest.mark_shed();
self.total_sheds.fetch_add(1, Ordering::Relaxed);
tracing::warn!(
core_id = slowest.core_id(),
pinned_mb = slowest.pinned_bytes() / (1024 * 1024),
total_mb = total / (1024 * 1024),
limit_mb = self.limit / (1024 * 1024),
"slab budget exceeded — shedding slowest consumer"
);
Some(slowest.core_id())
}
pub fn limit(&self) -> u64 {
self.limit
}
pub fn total_sheds(&self) -> u64 {
self.total_sheds.load(Ordering::Relaxed)
}
}
impl Default for SlabBudget {
fn default() -> Self {
Self::new()
}
}
fn detect_system_memory_bytes() -> Option<u64> {
let contents = std::fs::read_to_string("/proc/meminfo").ok()?;
for line in contents.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
let kb_str = rest.trim().strip_suffix("kB")?.trim();
let kb: u64 = kb_str.parse().ok()?;
return Some(kb * 1024);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn within_budget_no_shed() {
let budget = SlabBudget::with_limit(1000);
let a0 = ConsumerSlabAccount::new(0);
let a1 = ConsumerSlabAccount::new(1);
a0.add_pinned(200);
a1.add_pinned(300);
assert!(budget.check_and_shed(&[&a0, &a1]).is_none());
}
#[test]
fn over_budget_sheds_slowest() {
let budget = SlabBudget::with_limit(1000);
let a0 = ConsumerSlabAccount::new(0);
let a1 = ConsumerSlabAccount::new(1);
a0.add_pinned(400);
a1.add_pinned(700);
let shed = budget.check_and_shed(&[&a0, &a1]);
assert_eq!(shed, Some(1)); assert!(a1.is_shed());
assert!(!a0.is_shed());
assert_eq!(budget.total_sheds(), 1);
}
#[test]
fn already_shed_not_re_shed() {
let budget = SlabBudget::with_limit(1000);
let a0 = ConsumerSlabAccount::new(0);
let a1 = ConsumerSlabAccount::new(1);
a0.add_pinned(600);
a1.add_pinned(700);
a1.mark_shed();
let shed = budget.check_and_shed(&[&a0, &a1]);
assert_eq!(shed, Some(0));
}
#[test]
fn release_pinned_bytes() {
let account = ConsumerSlabAccount::new(0);
account.add_pinned(500);
assert_eq!(account.pinned_bytes(), 500);
account.release_pinned(200);
assert_eq!(account.pinned_bytes(), 300);
account.reset();
assert_eq!(account.pinned_bytes(), 0);
}
#[test]
fn shed_and_clear() {
let account = ConsumerSlabAccount::new(0);
assert!(!account.is_shed());
account.mark_shed();
assert!(account.is_shed());
account.clear_shed();
assert!(!account.is_shed());
}
#[test]
fn default_budget_128mb() {
let budget = SlabBudget::new();
assert_eq!(budget.limit(), 128 * 1024 * 1024);
}
#[test]
fn for_cores_auto_tunes() {
let budget = SlabBudget::for_cores(4);
assert!(budget.limit() >= MIN_BUDGET_PER_CORE);
assert!(budget.limit() <= MAX_BUDGET_PER_CORE);
}
#[test]
fn for_cores_zero_treated_as_one() {
let budget = SlabBudget::for_cores(0);
assert!(budget.limit() >= MIN_BUDGET_PER_CORE);
}
#[test]
fn detect_system_memory() {
if cfg!(target_os = "linux") {
let mem = detect_system_memory_bytes();
assert!(mem.is_some(), "should detect memory on Linux");
assert!(mem.unwrap() > 0);
}
}
}