#![allow(
clippy::too_many_arguments,
clippy::manual_is_multiple_of,
clippy::manual_clamp,
clippy::let_and_return
)]
#![cfg(all(feature = "alloc-global", feature = "alloc-xthread"))]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::semicolon_if_nothing_returned
)]
use std::alloc::{GlobalAlloc, Layout};
use std::env;
use std::hint::black_box;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::{Duration, Instant};
use sefer_alloc::SeferAlloc;
#[global_allocator]
static GLOBAL: SeferAlloc = SeferAlloc::new();
const MAX_THREADS: usize = 128;
const WORKING_SET: usize = 512;
const LARGE_THRESHOLD: usize = 2 * 1024 * 1024;
const HANDOFF_EVERY: usize = 32;
const LARGE_EVERY: usize = 512;
const HEARTBEAT_SECS: u64 = 10;
struct Block {
ptr: *mut u8,
layout: Layout,
}
unsafe impl Send for Block {}
struct Xrs64(u64);
impl Xrs64 {
fn new(seed: u64) -> Self {
Self(seed | 1) }
#[inline]
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
#[inline]
fn below(&mut self, n: usize) -> usize {
(self.next_u64() % n as u64) as usize
}
}
#[inline]
fn pick_small_size(rng: &mut Xrs64) -> usize {
8 + rng.below(57) }
#[inline]
fn pick_mixed_size(rng: &mut Xrs64) -> usize {
let r = rng.next_u64();
if r.is_multiple_of(32) {
512 + (r >> 8) as usize % (8 * 1024 - 512)
} else {
16 + (r >> 8) as usize % (512 - 16)
}
}
#[inline]
fn pick_large_size(rng: &mut Xrs64) -> usize {
LARGE_THRESHOLD + rng.below(LARGE_THRESHOLD)
}
#[inline]
fn layout_for(size: usize) -> Layout {
Layout::from_size_align(size.max(1), 8).unwrap()
}
#[inline]
unsafe fn alloc_block_sized<A: GlobalAlloc>(a: &A, size: usize) -> Block {
let layout = layout_for(size);
let ptr = unsafe { a.alloc(layout) };
if !ptr.is_null() {
unsafe { ptr.write(0xA5) };
}
Block { ptr, layout }
}
#[inline]
unsafe fn free_block<A: GlobalAlloc>(a: &A, block: Block) {
if !block.ptr.is_null() {
unsafe { a.dealloc(block.ptr, block.layout) };
}
}
#[inline]
unsafe fn drain_mailbox<A: GlobalAlloc>(a: &A, rx: &Receiver<Block>, count: &mut u64) {
while let Ok(block) = rx.try_recv() {
unsafe { free_block(a, block) };
*count += 1;
}
}
#[derive(Default, Debug)]
struct WorkerCounts {
alloc: u64,
free_local: u64,
free_xthread: u64,
large: u64,
}
unsafe fn soak_worker<A: GlobalAlloc>(
a: &A,
seed: u64,
thread_idx: usize,
n_threads: usize,
senders: &[Sender<Block>],
rx: &Receiver<Block>,
stop: &AtomicBool,
ops_counter: &AtomicU64,
) -> WorkerCounts {
let mut rng = Xrs64::new(seed);
let mut counts = WorkerCounts::default();
let mut slots: Vec<Option<Block>> = (0..WORKING_SET)
.map(|_| {
let sz = pick_mixed_size(&mut rng);
Some(unsafe { alloc_block_sized(a, sz) })
})
.collect();
counts.alloc += WORKING_SET as u64;
let mut op_tick: u64 = 0;
while !stop.load(Ordering::Relaxed) {
let before = counts.free_xthread;
unsafe { drain_mailbox(a, rx, &mut counts.free_xthread) };
let drained = counts.free_xthread - before;
if drained > 0 {
op_tick += drained;
}
let r = rng.next_u64();
let op_class = r % 16;
if op_tick % LARGE_EVERY as u64 == 0 {
let sz = pick_large_size(&mut rng);
let big = unsafe { alloc_block_sized(a, sz) };
counts.alloc += 1;
counts.large += 1;
black_box(big.ptr);
unsafe { free_block(a, big) };
counts.free_local += 1;
}
if n_threads > 1 && op_tick % HANDOFF_EVERY as u64 == 0 {
let idx = rng.below(WORKING_SET);
if let Some(block) = slots[idx].take() {
let mut target = rng.below(n_threads);
if target == thread_idx {
target = (target + 1) % n_threads;
}
match senders[target].send(block) {
Ok(()) => {
}
Err(unsent) => {
unsafe { free_block(a, unsent.0) };
counts.free_local += 1;
}
}
let sz = if op_class < 4 {
pick_small_size(&mut rng)
} else {
pick_mixed_size(&mut rng)
};
slots[idx] = Some(unsafe { alloc_block_sized(a, sz) });
counts.alloc += 1;
op_tick += 1;
ops_counter.fetch_add(1, Ordering::Relaxed);
continue;
}
}
let idx = rng.below(WORKING_SET);
if let Some(block) = slots[idx].take() {
unsafe { free_block(a, block) };
counts.free_local += 1;
}
let sz = if op_class < 6 {
pick_small_size(&mut rng)
} else {
pick_mixed_size(&mut rng)
};
slots[idx] = Some(unsafe { alloc_block_sized(a, sz) });
counts.alloc += 1;
op_tick += 1;
ops_counter.fetch_add(1, Ordering::Relaxed);
}
for slot in slots.drain(..).flatten() {
unsafe { free_block(a, slot) };
counts.free_local += 1;
}
unsafe { drain_mailbox(a, rx, &mut counts.free_xthread) };
black_box(&counts.alloc);
counts
}
fn main() {
let soak_seconds: u64 = env::var("SEFER_SOAK_SECONDS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(5);
let avail = thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let n_threads: usize = env::var("SEFER_SOAK_THREADS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(avail)
.min(MAX_THREADS)
.max(1);
let run_duration = Duration::from_secs(soak_seconds);
println!("== sefer-alloc soak harness ==");
println!("threads={n_threads} duration={soak_seconds}s working_set/thread={WORKING_SET}");
println!("(set SEFER_SOAK_THREADS=N SEFER_SOAK_SECONDS=N to override)\n");
let stop = Arc::new(AtomicBool::new(false));
let ops_counters: Arc<Vec<AtomicU64>> =
Arc::new((0..n_threads).map(|_| AtomicU64::new(0)).collect());
let mut senders: Vec<Sender<Block>> = Vec::with_capacity(n_threads);
let mut receivers: Vec<Option<Receiver<Block>>> = Vec::with_capacity(n_threads);
for _ in 0..n_threads {
let (tx, rx) = channel::<Block>();
senders.push(tx);
receivers.push(Some(rx));
}
let senders = Arc::new(senders);
let barrier = Arc::new(Barrier::new(n_threads + 1));
let mut handles = Vec::with_capacity(n_threads);
for (t, rx_slot) in receivers.iter_mut().enumerate() {
let stop_t = Arc::clone(&stop);
let senders_t = Arc::clone(&senders);
let barrier_t = Arc::clone(&barrier);
let ops_t = Arc::clone(&ops_counters);
let rx = rx_slot.take().unwrap();
let seed = 0x9E37_79B9_7F4A_7C15u64
.wrapping_mul(t as u64 + 1)
.wrapping_add(0xDEAD_BEEF_CAFE_1234);
let handle = thread::spawn(move || {
let alloc = SeferAlloc::new();
barrier_t.wait();
let counts = unsafe {
soak_worker(
&alloc, seed, t, n_threads, &senders_t, &rx, &stop_t, &ops_t[t],
)
};
counts
});
handles.push(handle);
}
barrier.wait();
let start = Instant::now();
let mut last_heartbeat = start;
let mut last_ops: u64 = 0;
loop {
let elapsed = start.elapsed();
if elapsed >= run_duration {
break;
}
let now = Instant::now();
let since_last = now.duration_since(last_heartbeat);
if since_last >= Duration::from_secs(HEARTBEAT_SECS) {
let total_ops: u64 = ops_counters.iter().map(|c| c.load(Ordering::Relaxed)).sum();
let delta = total_ops.saturating_sub(last_ops);
let rate = delta as f64 / since_last.as_secs_f64() / 1_000_000.0;
println!(
"[T+{:.0}s] {} threads alive, ops={} ({:.2} M/s)",
elapsed.as_secs_f64(),
n_threads,
total_ops,
rate,
);
last_heartbeat = now;
last_ops = total_ops;
}
thread::sleep(Duration::from_millis(200));
}
stop.store(true, Ordering::Relaxed);
let elapsed = start.elapsed();
let mut total_alloc: u64 = 0;
let mut total_free_local: u64 = 0;
let mut total_free_xthread: u64 = 0;
let mut total_large: u64 = 0;
for (i, h) in handles.into_iter().enumerate() {
match h.join() {
Ok(counts) => {
total_alloc += counts.alloc;
total_free_local += counts.free_local;
total_free_xthread += counts.free_xthread;
total_large += counts.large;
}
Err(_) => {
eprintln!("[soak] worker {i} panicked — aborting");
std::process::exit(1);
}
}
}
drop(senders);
let total_ops: u64 = ops_counters.iter().map(|c| c.load(Ordering::Relaxed)).sum();
let rate = total_ops as f64 / elapsed.as_secs_f64() / 1_000_000.0;
println!();
println!("== soak complete ==");
println!(" elapsed: {:.2}s", elapsed.as_secs_f64());
println!(" threads: {n_threads}");
println!(" total_alloc: {total_alloc}");
println!(" total_free_local:{total_free_local}");
println!(" total_free_xth: {total_free_xthread}");
println!(" total_large: {total_large}");
println!(" total_ops: {total_ops} ({rate:.2} M/s aggregate)");
let total_free = total_free_local + total_free_xthread;
if total_alloc != total_free {
eprintln!();
eprintln!(
"[soak] INVARIANT VIOLATED: alloc={total_alloc} != free={total_free} \
(local={total_free_local} xthread={total_free_xthread})"
);
eprintln!("[soak] This indicates a leak or double-free in the handoff logic.");
std::process::exit(2);
}
println!();
println!("[soak] invariant OK: alloc={total_alloc} == free={total_free} (local={total_free_local} + xthread={total_free_xthread})");
println!("[soak] exit 0 — all checks passed");
}