#![cfg(feature = "alloc-global")]
use std::alloc::GlobalAlloc;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use sefer_alloc::SeferAlloc;
#[global_allocator]
static GLOBAL: SeferAlloc = SeferAlloc::new();
static SERIAL: AtomicBool = AtomicBool::new(false);
struct SerialGuard;
impl SerialGuard {
fn acquire() -> Self {
while SERIAL
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
std::hint::spin_loop();
}
SerialGuard
}
}
impl Drop for SerialGuard {
fn drop(&mut self) {
SERIAL.store(false, Ordering::Release);
}
}
fn churn(worker_id: u64, rounds: u32) -> u64 {
let mut acc: u64 = worker_id;
for r in 0..rounds {
let mut v: Vec<u64> = Vec::new();
for i in 0..256u64 {
v.push(i.wrapping_add(acc).wrapping_add(r as u64));
}
acc = acc.wrapping_add(v.iter().copied().sum::<u64>() / 256);
let s = format!("worker-{worker_id}-round-{r}-acc-{acc}");
acc = acc.wrapping_add(s.len() as u64);
let b = Box::new(acc.wrapping_mul(7));
acc = acc.wrapping_add(*b);
let mut m: HashMap<u64, u64> = HashMap::new();
for i in 0..32u64 {
m.insert(i, acc.wrapping_add(i));
}
if let Some(&val) = m.get(&(r as u64 % 32)) {
acc = acc.wrapping_add(val);
}
}
acc
}
#[test]
fn global_allocator_serves_multithreaded_churn_with_thread_exit() {
let _serial = SerialGuard::acquire();
const WAVE_SIZE: usize = 4;
const ROUNDS: u32 = 8;
const WAVES: usize = 3;
let total = Arc::new(AtomicU64::new(0));
let mut expected_total: u64 = 0;
for wave in 0..WAVES {
let handles: Vec<_> = (0..WAVE_SIZE)
.map(|i| {
let worker_id = (wave * WAVE_SIZE + i) as u64;
let total = Arc::clone(&total);
std::thread::spawn(move || {
let acc = churn(worker_id, ROUNDS);
total.fetch_add(acc, Ordering::Relaxed);
acc
})
})
.collect();
for (i, h) in handles.into_iter().enumerate() {
let acc = h.join().expect("worker thread must not abort/panic");
expected_total = expected_total.wrapping_add(acc);
assert!(acc != 0 || i == 0, "worker returned a zero checksum");
}
}
let observed = total.load(Ordering::Acquire);
assert_eq!(
observed, expected_total,
"multithreaded churn checksum mismatch — the allocator corrupted/lost \
an allocation (UAF, double-free, or the №7 double-ownership landmine)"
);
}
#[cfg(feature = "alloc-xthread")]
#[test]
fn global_allocator_cross_thread_free() {
let _serial = SerialGuard::acquire();
const N_PRODUCERS: usize = 3;
const N_PER_PRODUCER: usize = 128;
let (tx, rx) = std::sync::mpsc::channel::<Box<u64>>();
let mut expected: u64 = 0;
let producers: Vec<_> = (0..N_PRODUCERS)
.map(|p| {
let tx = tx.clone();
std::thread::spawn(move || {
let mut local = 0u64;
for i in 0..N_PER_PRODUCER {
let val = ((p * N_PER_PRODUCER + i) as u64).wrapping_mul(13);
local = local.wrapping_add(val);
if tx.send(Box::new(val)).is_err() {
return local;
}
}
local
})
})
.collect();
drop(tx);
let mut observed: u64 = 0;
for b in rx {
observed = observed.wrapping_add(*b);
}
for h in producers {
let local = h.join().expect("producer must not abort");
expected = expected.wrapping_add(local);
}
assert_eq!(
observed, expected,
"cross-thread free checksum mismatch — a box was lost, corrupted, or \
double-freed under TFS routing + shard reuse"
);
}
#[test]
fn global_allocator_multithreaded_size_class_churn() {
let _serial = SerialGuard::acquire();
let sizes = [16usize, 32, 64, 128, 256, 512, 1024, 2048];
let n_threads = 4;
let n_iters = 2_000;
let handles: Vec<_> = (0..n_threads)
.map(|t| {
std::thread::spawn(move || {
for iter in 0..n_iters {
for &size in &sizes {
let layout = std::alloc::Layout::from_size_align(size, 8).unwrap();
let p = unsafe { GLOBAL.alloc(layout) };
assert!(
!p.is_null(),
"alloc({size}) returned null on t{t}/iter{iter}"
);
unsafe {
std::ptr::write_bytes(p, 0xAB, size);
assert_eq!(
(p as *const u8).read(),
0xAB,
"byte not retained at size {size} (free-list corruption?)"
);
GLOBAL.dealloc(p, layout);
}
}
}
})
})
.collect();
for h in handles {
h.join().expect("size-class churn thread must not abort");
}
}