#![cfg(all(feature = "allocator", feature = "allocation-tracking"))]
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::sync::atomic::{AtomicI64, Ordering};
use crate::mem::registry;
static GLOBAL_TOTAL_BYTES: AtomicI64 = AtomicI64::new(0);
const BATCH_THRESHOLD: i64 = 12 * 1024;
const MAX_DEPTH: u32 = 3;
thread_local! {
static THREAD_STATE: ThreadState = const { ThreadState::new() };
static RECURSION_DEPTH: Cell<u32> = const { Cell::new(0) };
}
struct ThreadState {
local_bytes: Cell<i64>,
}
impl ThreadState {
const fn new() -> Self {
Self {
local_bytes: Cell::new(0),
}
}
fn flush_to_global(&self) {
let delta = self.local_bytes.get();
if delta != 0 {
GLOBAL_TOTAL_BYTES.fetch_add(delta, Ordering::Relaxed);
self.local_bytes.set(0);
}
}
}
pub struct TrackAlloc<Alloc = System> {
alloc: Alloc,
}
impl<A> TrackAlloc<A> {
#[inline]
pub const fn new(alloc: A) -> Self {
Self {
alloc,
}
}
}
impl<A: GlobalAlloc> TrackAlloc<A> {
pub fn memory_allocated(&self) -> usize {
let heap_memory = GLOBAL_TOTAL_BYTES.load(Ordering::Relaxed).max(0) as usize;
let external_memory = registry::memory_reporters_allocated_total();
heap_memory + external_memory
}
pub fn flush_local_allocations(&self) {
THREAD_STATE.with(|state| {
state.flush_to_global();
});
}
pub fn is_beyond_threshold(&self) -> bool {
match *crate::cnf::MEMORY_THRESHOLD {
0 => false,
v => self.memory_allocated() > v,
}
}
fn add(&self, size: usize) {
let depth = RECURSION_DEPTH.with(|d| {
let current = d.get();
if current >= MAX_DEPTH {
return MAX_DEPTH;
}
d.set(current + 1);
current
});
if depth >= MAX_DEPTH {
return;
}
THREAD_STATE.with(|state| {
let bytes = state.local_bytes.get() + size as i64;
state.local_bytes.set(bytes);
if bytes >= BATCH_THRESHOLD {
state.flush_to_global();
}
});
RECURSION_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
}
fn sub(&self, size: usize) {
let depth = RECURSION_DEPTH.with(|d| {
let current = d.get();
if current >= MAX_DEPTH {
return MAX_DEPTH;
}
d.set(current + 1);
current
});
if depth >= MAX_DEPTH {
return;
}
THREAD_STATE.with(|state| {
let bytes = state.local_bytes.get() - size as i64;
state.local_bytes.set(bytes);
if bytes <= -BATCH_THRESHOLD {
state.flush_to_global();
}
});
RECURSION_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
}
}
unsafe impl<A: GlobalAlloc> GlobalAlloc for TrackAlloc<A> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ret = unsafe { self.alloc.alloc(layout) };
if !ret.is_null() {
self.add(layout.size());
}
ret
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { self.alloc.dealloc(ptr, layout) };
self.sub(layout.size());
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let ret = unsafe { self.alloc.alloc_zeroed(layout) };
if !ret.is_null() {
self.add(layout.size());
}
ret
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let ret = unsafe { self.alloc.realloc(ptr, layout, new_size) };
if !ret.is_null() {
self.sub(layout.size());
self.add(new_size);
}
ret
}
}