use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, mpsc};
use rusty_alloc::alloc::{collect, free, malloc, stats};
fn churn(seed: u64, ops: usize, sink: Option<&mpsc::Sender<usize>>) {
let mut s = seed | 1;
let mut rng = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let mut live: Vec<(*mut u8, usize)> = Vec::new();
for i in 0..ops {
let r = rng() % 100;
if !live.is_empty() && (live.len() > 300 || r < 40) {
let idx = (rng() as usize) % live.len();
let (p, size) = live.swap_remove(idx);
unsafe {
assert_eq!(p.read(), 0x5A, "canary lost before free");
if let Some(tx) = sink
&& rng() % 4 == 0
{
let _ = tx.send(p as usize); continue;
}
let _ = size;
free(p);
}
} else {
let size = match rng() % 1000 {
0..=850 => 8 + (rng() as usize % 500),
851..=960 => 1024 + (rng() as usize % 60_000),
961..=995 => 100_000 + (rng() as usize % 2_000_000),
_ => 17 * 1024 * 1024 + (rng() as usize % (8 * 1024 * 1024)),
};
let p = malloc(size);
assert!(!p.is_null(), "OOM at op {i}");
unsafe {
p.write(0x5A);
p.add(size - 1).write(0xA5);
}
live.push((p, size));
}
if i % 5000 == 4999 {
collect(true);
}
}
for (p, _) in live.drain(..) {
unsafe { free(p) };
}
}
#[test]
fn abandon_adopt_reuse_storm() {
let rounds = if cfg!(miri) { 1 } else { 40 };
let stop = Arc::new(AtomicBool::new(false));
let leaked = Arc::new(AtomicUsize::new(0));
let adopters: Vec<_> = (0..3)
.map(|k| {
let stop = stop.clone();
std::thread::spawn(move || {
let mut n = 0u64;
while !stop.load(Ordering::Relaxed) {
churn(0xA0000 + k * 7 + n, 3000, None);
n += 1;
}
})
})
.collect();
for round in 0..rounds {
let (tx, rx) = mpsc::channel::<usize>();
let dying: Vec<_> = (0..4)
.map(|t| {
let tx = tx.clone();
let leaked = leaked.clone();
std::thread::spawn(move || {
churn(0xBEEF + round * 31 + t, 2000, Some(&tx));
let mut kept = Vec::new();
for j in 0..8 {
let size = 1000 + j * 4096;
let p = malloc(size);
unsafe { core::ptr::write_bytes(p, 0x77, size) };
kept.push((p as usize, size));
}
leaked.fetch_add(kept.len(), Ordering::Relaxed);
kept
})
})
.collect();
drop(tx);
let mut bled = 0usize;
for addr in rx.iter() {
unsafe { free(addr as *mut u8) };
bled += 1;
}
for h in dying {
for (addr, size) in h.join().unwrap() {
let p = addr as *mut u8;
unsafe {
assert_eq!(p.read(), 0x77, "abandoned block corrupted");
assert_eq!(p.add(size - 1).read(), 0x77, "abandoned tail corrupted");
free(p);
}
}
}
assert!(bled > 0, "round {round}: no cross-thread frees happened");
collect(true);
}
stop.store(true, Ordering::Relaxed);
for a in adopters {
a.join().unwrap();
}
let s = stats();
println!(
"storm done: rounds={rounds} leaked-then-freed={} allocs={} reclaims={} segs={}(-{})",
leaked.load(Ordering::Relaxed),
s.allocs,
s.reclaims,
s.segments,
s.segments_freed
);
}