#![allow(dead_code)]
use std::{
error::Error,
fmt,
sync::{
Arc,
atomic::{AtomicU64, AtomicUsize, Ordering},
},
};
mod datafusion_pool;
pub(crate) use datafusion_pool::budgeted_session_context;
const ENFORCED_BUDGET_NUMERATOR: u128 = 9;
const ENFORCED_BUDGET_DENOMINATOR: u128 = 10;
#[derive(Debug)]
pub struct ConnectionMemoryBudget {
limit: Option<usize>,
used: AtomicUsize,
peak_used: AtomicUsize,
denials: AtomicU64,
}
impl ConnectionMemoryBudget {
pub fn measured() -> Arc<Self> {
Arc::new(Self {
limit: None,
used: AtomicUsize::new(0),
peak_used: AtomicUsize::new(0),
denials: AtomicU64::new(0),
})
}
pub fn with_limit(configured_bytes: u64) -> Arc<Self> {
debug_assert!(
configured_bytes > 0,
"with_limit expects a positive budget; 0 / unset means measured() at the call site"
);
let limit = (configured_bytes as u128 * ENFORCED_BUDGET_NUMERATOR
/ ENFORCED_BUDGET_DENOMINATOR) as usize;
Arc::new(Self {
limit: Some(limit),
used: AtomicUsize::new(0),
peak_used: AtomicUsize::new(0),
denials: AtomicU64::new(0),
})
}
pub fn from_budget_bytes(bytes: u64) -> Arc<Self> {
if bytes > 0 {
Self::with_limit(bytes)
} else {
Self::measured()
}
}
pub(crate) fn try_reserve(self: &Arc<Self>, n: usize) -> Result<Reservation, OverBudget> {
self.try_grow(n)?;
Ok(Reservation {
budget: Arc::clone(self),
size: n,
})
}
pub(crate) fn try_grow(&self, n: usize) -> Result<(), OverBudget> {
match self.limit {
None => {
let prev = self.used.fetch_add(n, Ordering::Relaxed);
self.record_peak(prev.saturating_add(n));
Ok(())
}
Some(limit) => self
.used
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |used| {
used.checked_add(n).filter(|next| *next <= limit)
})
.map(|prev| self.record_peak(prev.saturating_add(n)))
.map_err(|used| {
self.denials.fetch_add(1, Ordering::Relaxed);
OverBudget {
requested: n,
used,
limit,
}
}),
}
}
pub(crate) fn grow_unchecked(&self, n: usize) {
let prev = self.used.fetch_add(n, Ordering::Relaxed);
self.record_peak(prev.saturating_add(n));
}
fn record_peak(&self, used_now: usize) {
self.peak_used.fetch_max(used_now, Ordering::Relaxed);
}
pub(crate) fn release(&self, n: usize) {
self.used.fetch_sub(n, Ordering::Relaxed);
}
pub(crate) fn used(&self) -> usize {
self.used.load(Ordering::Relaxed)
}
test_visible! {
fn peak(&self) -> usize {
self.peak_used.load(Ordering::Relaxed)
}
}
test_visible! {
fn used_bytes(&self) -> usize {
self.used()
}
}
test_visible! {
fn limit(&self) -> Option<usize> {
self.limit
}
}
test_visible! {
fn denials(&self) -> u64 {
self.denials.load(Ordering::Relaxed)
}
}
}
impl fmt::Display for ConnectionMemoryBudget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.limit {
Some(limit) => write!(
f,
"connection-memory-budget(used: {} B, limit: {limit} B)",
self.used()
),
None => write!(
f,
"connection-memory-budget(used: {} B, measured)",
self.used()
),
}
}
}
#[derive(Debug)]
pub(crate) struct Reservation {
budget: Arc<ConnectionMemoryBudget>,
size: usize,
}
impl Reservation {
pub(crate) fn try_grow(&mut self, extra: usize) -> Result<(), OverBudget> {
self.budget.try_grow(extra)?;
self.size += extra;
Ok(())
}
pub(crate) fn size(&self) -> usize {
self.size
}
}
impl Drop for Reservation {
fn drop(&mut self) {
self.budget.release(self.size);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct OverBudget {
pub requested: usize,
pub used: usize,
pub limit: usize,
}
impl fmt::Display for OverBudget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"over connection memory budget: requested {} B with {} B in use of a {} B limit",
self.requested, self.used, self.limit
)
}
}
impl Error for OverBudget {}
#[cfg(test)]
mod tests {
use std::{sync::Arc, thread};
use super::*;
const KB: u64 = 1024;
#[test]
fn measured_tracks_but_never_denies() {
let budget = ConnectionMemoryBudget::measured();
assert_eq!(budget.limit(), None);
let huge = usize::MAX / 2;
let r1 = budget.try_reserve(huge).expect("measured never denies");
let r2 = budget.try_reserve(huge).expect("measured never denies");
assert_eq!(budget.used(), huge * 2);
assert_eq!(budget.denials(), 0);
drop((r1, r2));
assert_eq!(budget.used(), 0);
}
#[test]
fn peak_holds_the_largest_used() {
let budget = ConnectionMemoryBudget::measured();
let r1 = budget.try_reserve(1000).expect("ok");
let r2 = budget.try_reserve(500).expect("ok");
assert_eq!(budget.used(), 1500);
assert_eq!(budget.peak(), 1500);
drop((r1, r2));
assert_eq!(budget.used(), 0);
assert_eq!(budget.peak(), 1500);
let r3 = budget.try_reserve(200).expect("ok");
assert_eq!(budget.peak(), 1500);
drop(r3);
}
#[test]
fn with_limit_bakes_in_the_headroom_gate() {
let budget = ConnectionMemoryBudget::with_limit(1000);
assert_eq!(budget.limit(), Some(900));
}
#[test]
fn from_budget_bytes_maps_zero_to_measured_and_positive_to_bounded() {
assert_eq!(ConnectionMemoryBudget::from_budget_bytes(0).limit(), None);
assert_eq!(
ConnectionMemoryBudget::from_budget_bytes(1000).limit(),
Some(900)
);
}
#[test]
fn bounded_allows_up_to_the_gate_and_denies_past_it() {
let budget = ConnectionMemoryBudget::with_limit(1000); let held = budget.try_reserve(900).expect("exactly at the gate fits");
assert_eq!(budget.used(), 900);
assert_eq!(budget.peak(), 900);
let err = budget
.try_reserve(1)
.expect_err("one byte over the gate is denied");
assert_eq!(
err,
OverBudget {
requested: 1,
used: 900,
limit: 900
}
);
assert_eq!(budget.denials(), 1);
assert_eq!(budget.used(), 900);
assert_eq!(budget.peak(), 900);
drop(held);
assert_eq!(budget.used(), 0);
budget.try_reserve(900).expect("budget freed on drop");
}
#[test]
fn reservation_drop_frees_exactly_what_it_held() {
let budget = ConnectionMemoryBudget::with_limit(10 * KB); {
let _a = budget.try_reserve(4000).expect("fits");
let _b = budget.try_reserve(4000).expect("fits");
assert_eq!(budget.used(), 8000);
}
assert_eq!(budget.used(), 0);
}
#[test]
fn reservation_grows_in_place_and_denies_when_full() {
let budget = ConnectionMemoryBudget::with_limit(1000); let mut r = budget.try_reserve(500).expect("fits");
r.try_grow(400).expect("500 + 400 = 900 fits");
assert_eq!(r.size(), 900);
assert_eq!(budget.used(), 900);
r.try_grow(1).expect_err("over the gate");
assert_eq!(r.size(), 900);
assert_eq!(budget.used(), 900);
}
#[test]
fn grow_unchecked_ignores_the_gate() {
let budget = ConnectionMemoryBudget::with_limit(1000); budget.grow_unchecked(5000); assert_eq!(budget.used(), 5000);
assert_eq!(budget.peak(), 5000); assert_eq!(budget.denials(), 0); budget.release(5000);
assert_eq!(budget.used(), 0);
}
#[test]
fn display_shows_mode_and_usage() {
let bounded = ConnectionMemoryBudget::with_limit(1000); let _held = bounded.try_reserve(100).expect("fits");
assert_eq!(
format!("{bounded}"),
"connection-memory-budget(used: 100 B, limit: 900 B)"
);
let measured = ConnectionMemoryBudget::measured();
assert_eq!(
format!("{measured}"),
"connection-memory-budget(used: 0 B, measured)"
);
}
#[test]
fn concurrent_reservations_never_overcommit() {
let budget = ConnectionMemoryBudget::with_limit(1000); let threads = 32;
let chunk = 100;
let handles: Vec<_> = (0..threads)
.map(|_| {
let budget = Arc::clone(&budget);
thread::spawn(move || budget.try_reserve(chunk).ok())
})
.collect();
let held: Vec<_> = handles
.into_iter()
.filter_map(|h| h.join().expect("thread panicked"))
.collect();
assert_eq!(held.len(), 9, "exactly nine 100-byte holds fit under 900");
assert_eq!(budget.used(), 900);
assert_eq!(budget.denials() as usize, threads - 9);
drop(held);
assert_eq!(budget.used(), 0);
}
#[test]
fn concurrent_churn_balances_back_to_zero() {
let budget = ConnectionMemoryBudget::with_limit(10_000); let handles: Vec<_> = (0..16)
.map(|_| {
let budget = Arc::clone(&budget);
thread::spawn(move || {
for _ in 0..1000 {
drop(budget.try_reserve(500));
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
assert_eq!(
budget.used(),
0,
"every reservation released; nothing leaked"
);
}
}