use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Admission {
Admit,
Defer,
Reduce {
batch_shape: BatchShape,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BatchShape {
pub max_items: usize,
}
impl BatchShape {
pub fn empty() -> Self {
Self { max_items: 0 }
}
pub fn half_of(items: usize) -> Self {
Self {
max_items: items / 2,
}
}
pub fn scaled_for_bytes(default_items: usize, headroom_mb: u64, estimate_mb: u64) -> Self {
if estimate_mb == 0 || estimate_mb <= headroom_mb {
return Self {
max_items: default_items,
};
}
let scaled = (default_items as u128)
.saturating_mul(headroom_mb as u128)
.checked_div(estimate_mb as u128)
.unwrap_or(0);
Self {
max_items: scaled.min(default_items as u128) as usize,
}
}
}
pub type AdmissionDecider = Box<dyn Fn(usize) -> Admission + Send + Sync>;
pub fn always_admit() -> AdmissionDecider {
Box::new(|_| Admission::Admit)
}
pub fn defer_above(defer_above_bytes: usize) -> AdmissionDecider {
Box::new(move |estimate| {
if estimate > defer_above_bytes {
Admission::Defer
} else {
Admission::Admit
}
})
}
fn ceil_mb(bytes: u64) -> u64 {
const MB: u64 = 1024 * 1024;
if bytes == 0 { 0 } else { bytes.div_ceil(MB) }
}
fn read_mmap_resident_mb() -> u64 {
let Ok(content) = std::fs::read_to_string("/proc/self/smaps") else {
return 0;
};
let mut rss_kb: u64 = 0;
let mut in_file_mapping = false;
for line in content.lines() {
let trimmed = line.trim();
if !trimmed.is_empty()
&& trimmed
.chars()
.next()
.is_some_and(|c| c.is_ascii_hexdigit())
&& trimmed.contains('-')
{
let fields: Vec<&str> = trimmed.split_whitespace().collect();
in_file_mapping = fields.len() > 5 && !fields[5].starts_with('[');
continue;
}
if let Some(rest) = trimmed.strip_prefix("Rss:") {
if in_file_mapping {
if let Ok(kb) = rest.split_whitespace().next().unwrap_or("0").parse::<u64>() {
rss_kb += kb;
}
}
}
}
rss_kb / 1024
}
fn read_self_rss_mb() -> Result<u64, String> {
let status = std::fs::read_to_string("/proc/self/status")
.map_err(|e| format!("cannot read /proc/self/status: {e}"))?;
for line in status.lines() {
if line.starts_with("VmRSS:") {
let kb = line
.split_whitespace()
.nth(1)
.ok_or_else(|| "malformed VmRSS line".to_string())?
.parse::<u64>()
.map_err(|_| "non-numeric VmRSS value".to_string())?;
return Ok(kb / 1024);
}
}
Err("VmRSS not found in /proc/self/status".to_string())
}
pub struct AdmissionController {
rss_reader: Box<dyn Fn() -> Result<u64, String> + Send + Sync>,
mmap_resident: Arc<AtomicU64>,
provider_reserve: u64,
cap: u64,
reduce_min_headroom_mb: u64,
default_max_items: usize,
on_pressure: Box<dyn Fn() + Send + Sync>,
}
impl AdmissionController {
pub fn new(
cap_mb: u64,
provider_reserve_mb: u64,
rss_reader: impl Fn() -> Result<u64, String> + Send + Sync + 'static,
) -> Self {
Self {
rss_reader: Box::new(rss_reader),
mmap_resident: Arc::new(AtomicU64::new(0)),
provider_reserve: provider_reserve_mb,
cap: cap_mb,
reduce_min_headroom_mb: cap_mb / 10,
default_max_items: 1000,
on_pressure: Box::new(|| {}),
}
}
pub fn real(cap_mb: u64, provider_reserve_mb: u64) -> Self {
let controller = Self::new(cap_mb, provider_reserve_mb, read_self_rss_mb);
controller
.mmap_resident
.store(read_mmap_resident_mb(), Ordering::SeqCst);
controller
}
pub fn mmap_resident_cell(&self) -> Arc<AtomicU64> {
self.mmap_resident.clone()
}
pub fn with_eviction(mut self, on_pressure: impl Fn() + Send + Sync + 'static) -> Self {
self.on_pressure = Box::new(on_pressure);
self
}
pub fn with_default_items(mut self, items: usize) -> Self {
self.default_max_items = items;
self
}
pub fn cap_mb(&self) -> u64 {
self.cap
}
fn base_usage_mb(&self) -> u64 {
let rss = (self.rss_reader)().unwrap_or(0);
rss.saturating_add(self.mmap_resident.load(Ordering::SeqCst))
.saturating_add(self.provider_reserve)
}
pub fn decide(&self, next_estimate: usize) -> Admission {
let estimate_mb = ceil_mb(next_estimate as u64);
let base = self.base_usage_mb();
if base.saturating_add(estimate_mb) <= self.cap {
return Admission::Admit;
}
(self.on_pressure)();
let base_after = self.base_usage_mb();
if base_after.saturating_add(estimate_mb) <= self.cap {
return Admission::Admit;
}
let headroom = self.cap.saturating_sub(base_after);
if headroom <= self.reduce_min_headroom_mb {
return Admission::Defer;
}
let shape = BatchShape::scaled_for_bytes(self.default_max_items, headroom, estimate_mb);
if shape.max_items == 0 {
Admission::Defer
} else {
Admission::Reduce { batch_shape: shape }
}
}
pub fn decider(self: &Arc<Self>) -> AdmissionDecider {
let controller = self.clone();
Box::new(move |estimate| controller.decide(estimate))
}
}
#[cfg(test)]
mod test {
use super::*;
use proptest::prelude::*;
const MB: u64 = 1024 * 1024;
fn ok_reader(rss_mb: u64) -> impl Fn() -> Result<u64, String> {
move || Ok(rss_mb)
}
fn rss_only(cap_mb: u64, rss_mb: u64) -> AdmissionController {
AdmissionController::new(cap_mb, 0, ok_reader(rss_mb))
}
proptest! {
#[test]
fn test_admission_never_errors(
cap in 16u64..=(1 << 20),
rss in 0u64..=(1 << 30),
mmap in 0u64..=(1 << 30),
provider in 0u64..=(1 << 30),
estimate_bytes in 0usize..=(1usize << 34),
) {
let controller = AdmissionController {
rss_reader: Box::new(move || Ok(rss)),
mmap_resident: Arc::new(AtomicU64::new(mmap)),
provider_reserve: provider,
cap,
reduce_min_headroom_mb: cap / 10,
default_max_items: 1000,
on_pressure: Box::new(|| {}),
};
let decision = controller.decide(estimate_bytes);
match decision {
Admission::Admit => {}
Admission::Defer => {}
Admission::Reduce { batch_shape } => {
prop_assert!(batch_shape.max_items <= 1000);
}
}
}
}
#[test]
fn test_real_readers_do_not_panic() {
let controller = AdmissionController::real(1 << 20, 0);
let decision = controller.decide(MB as usize);
matches!(
decision,
Admission::Admit | Admission::Defer | Admission::Reduce { .. }
);
#[cfg(target_os = "linux")]
{
let rss = read_self_rss_mb().expect("RSS readable on Linux");
assert!(rss > 0);
}
}
#[test]
fn test_admission_reads_all_factors() {
let cap = 1024;
let low = rss_only(cap, 100).decide(100 * MB as usize);
assert_eq!(low, Admission::Admit, "low RSS admits");
let near = rss_only(cap, 980).decide(256 * MB as usize);
assert_eq!(
near,
Admission::Defer,
"near-cap RSS + large estimate defers"
);
let controller = AdmissionController::new(cap, 0, ok_reader(0));
let mmap_cell = controller.mmap_resident_cell();
mmap_cell.store(100, Ordering::SeqCst);
assert_eq!(
controller.decide(100 * MB as usize),
Admission::Admit,
"small mmap working set admits"
);
mmap_cell.store(980, Ordering::SeqCst);
assert_eq!(
controller.decide(256 * MB as usize),
Admission::Defer,
"large mmap working set defers with RSS+provider constant"
);
let low_provider = AdmissionController::new(cap, 100, ok_reader(0));
assert_eq!(
low_provider.decide(100 * MB as usize),
Admission::Admit,
"small provider reserve admits"
);
let high_provider = AdmissionController::new(cap, 980, ok_reader(0));
assert_eq!(
high_provider.decide(256 * MB as usize),
Admission::Defer,
"large provider reserve defers with RSS+mmap constant"
);
}
#[test]
fn test_admission_defer_not_error() {
let cap = 1024;
let rss = Arc::new(AtomicU64::new(972)); let rss_for_reader = rss.clone();
let controller =
AdmissionController::new(cap, 0, move || Ok(rss_for_reader.load(Ordering::SeqCst)));
let estimate = 512 * MB as usize;
assert_eq!(
controller.decide(estimate),
Admission::Defer,
"near-cap + large estimate defers"
);
rss.store(400, Ordering::SeqCst);
assert_eq!(
controller.decide(estimate),
Admission::Admit,
"after memory frees, the deferred job is admitted"
);
}
#[test]
fn test_admission_evicts_under_pressure() {
let cap = 1024;
let evictions = Arc::new(AtomicU64::new(0));
let evictions_hook = evictions.clone();
let controller = AdmissionController::new(cap, 200, ok_reader(200));
let mmap_cell = controller.mmap_resident_cell();
mmap_cell.store(600, Ordering::SeqCst);
let hook_mmap = mmap_cell.clone();
let controller = controller.with_eviction(move || {
evictions_hook.fetch_add(1, Ordering::SeqCst);
hook_mmap.store(0, Ordering::SeqCst);
});
let estimate = 300 * MB as usize;
let decision = controller.decide(estimate);
assert_eq!(
evictions.load(Ordering::SeqCst),
1,
"eviction must be the first pressure response"
);
assert_eq!(
decision,
Admission::Admit,
"after eviction frees headroom the work is admitted"
);
}
#[test]
fn test_admission_defers_when_eviction_not_enough() {
let cap = 1024;
let evictions = Arc::new(AtomicU64::new(0));
let evictions_hook = evictions.clone();
let controller =
AdmissionController::new(cap, 200, ok_reader(900)).with_eviction(move || {
evictions_hook.fetch_add(1, Ordering::SeqCst);
});
let decision = controller.decide(100 * MB as usize);
assert_eq!(
evictions.load(Ordering::SeqCst),
1,
"eviction attempted before deferring"
);
assert_eq!(
decision,
Admission::Defer,
"still over cap after eviction → defer, never error"
);
}
#[test]
fn test_admission_reduces_batch_to_fit() {
let cap = 1024;
let controller = AdmissionController::new(cap, 0, ok_reader(200)).with_default_items(1000);
let estimate = 1024 * MB as usize;
match controller.decide(estimate) {
Admission::Reduce { batch_shape } => {
assert!(
batch_shape.max_items < 1000,
"reduced batch must shrink the item ceiling, got {}",
batch_shape.max_items
);
assert!(
batch_shape.max_items > 0,
"a fitting batch still makes progress"
);
}
other => panic!("expected Reduce, got {other:?}"),
}
}
#[test]
fn test_batch_shape_scaled_for_bytes() {
assert_eq!(BatchShape::scaled_for_bytes(1000, 800, 500).max_items, 1000);
let shape = BatchShape::scaled_for_bytes(1000, 400, 800);
assert_eq!(shape.max_items, 500);
assert_eq!(BatchShape::scaled_for_bytes(1000, 0, 800).max_items, 0);
assert_eq!(BatchShape::scaled_for_bytes(1000, 10, 0).max_items, 1000);
}
#[test]
fn test_controller_as_decider() {
let controller = Arc::new(rss_only(1024, 100));
let decider = controller.decider();
assert_eq!(decider(100 * MB as usize), Admission::Admit);
let near = Arc::new(rss_only(1024, 990));
assert_eq!(near.decider()(512 * MB as usize), Admission::Defer);
}
#[test]
fn test_defer_above_policy() {
let decider = defer_above(64);
assert_eq!(decider(10), Admission::Admit);
assert_eq!(decider(1000), Admission::Defer);
}
}