use std::ptr;
use std::sync::{Arc, Barrier};
use std::thread;
unsafe fn alloc() -> &'static compatmalloc::__test_support::HardenedAllocator {
compatmalloc::__test_support::ensure_initialized();
compatmalloc::__test_support::allocator()
}
fn stress_malloc_free_n_threads(num_threads: usize) {
const ITERATIONS: usize = 10_000;
const ALLOC_SIZE: usize = 128;
unsafe {
alloc();
}
let barrier = Arc::new(Barrier::new(num_threads));
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
unsafe {
let a = alloc();
for _ in 0..ITERATIONS {
let p = a.malloc(ALLOC_SIZE);
assert!(!p.is_null(), "malloc returned NULL under contention");
ptr::write_bytes(p, 0xCC, ALLOC_SIZE);
a.free(p);
}
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked during malloc/free stress");
}
}
#[test]
fn stress_malloc_free_4_threads() {
stress_malloc_free_n_threads(4);
}
#[test]
fn stress_malloc_free_8_threads() {
stress_malloc_free_n_threads(8);
}
#[test]
fn stress_malloc_free_16_threads() {
stress_malloc_free_n_threads(16);
}
#[derive(Clone, Copy)]
struct SendPtr(*mut u8);
unsafe impl Send for SendPtr {}
unsafe impl Sync for SendPtr {}
#[test]
fn cross_thread_free() {
const COUNT: usize = 1_000;
const SIZE: usize = 64;
unsafe {
alloc();
}
let barrier = Arc::new(Barrier::new(2));
let shared: Arc<std::sync::Mutex<Vec<SendPtr>>> =
Arc::new(std::sync::Mutex::new(Vec::with_capacity(COUNT)));
let shared_producer = Arc::clone(&shared);
let barrier_producer = Arc::clone(&barrier);
let producer = thread::spawn(move || {
barrier_producer.wait();
unsafe {
let a = alloc();
for _ in 0..COUNT {
let p = a.malloc(SIZE);
assert!(!p.is_null());
ptr::write_bytes(p, 0xDD, SIZE);
shared_producer.lock().unwrap().push(SendPtr(p));
}
}
});
let shared_consumer = Arc::clone(&shared);
let barrier_consumer = Arc::clone(&barrier);
let consumer = thread::spawn(move || {
barrier_consumer.wait();
unsafe {
let a = alloc();
let mut freed = 0;
while freed < COUNT {
let batch: Vec<SendPtr> = {
let mut guard = shared_consumer.lock().unwrap();
guard.drain(..).collect()
};
for sp in batch {
a.free(sp.0);
freed += 1;
}
if freed < COUNT {
thread::yield_now();
}
}
}
});
producer.join().expect("producer thread panicked");
consumer.join().expect("consumer thread panicked");
}
#[test]
fn no_data_corruption_under_contention() {
const NUM_THREADS: usize = 8;
const ITERATIONS: usize = 2_000;
const SIZE: usize = 256;
unsafe {
alloc();
}
let barrier = Arc::new(Barrier::new(NUM_THREADS));
let handles: Vec<_> = (0..NUM_THREADS)
.map(|tid| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
unsafe {
let a = alloc();
let pattern = (tid & 0xFF) as u8;
for _ in 0..ITERATIONS {
let p = a.malloc(SIZE);
assert!(!p.is_null());
ptr::write_bytes(p, pattern, SIZE);
let slice = std::slice::from_raw_parts(p, SIZE);
assert!(
slice.iter().all(|&b| b == pattern),
"data corruption detected: thread {} found unexpected byte",
tid
);
a.free(p);
}
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked during corruption check");
}
}
#[test]
fn various_sizes_under_contention() {
const NUM_THREADS: usize = 8;
const SIZES: [usize; 10] = [1, 16, 32, 64, 128, 256, 512, 1024, 4096, 16384];
unsafe {
alloc();
}
let barrier = Arc::new(Barrier::new(NUM_THREADS));
let handles: Vec<_> = (0..NUM_THREADS)
.map(|tid| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
unsafe {
let a = alloc();
for _ in 0..500 {
let size = SIZES[tid % SIZES.len()];
let p = a.malloc(size);
assert!(
!p.is_null(),
"malloc({}) returned NULL in thread {}",
size,
tid
);
ptr::write_bytes(p, 0xEE, size);
let slice = std::slice::from_raw_parts(p, size);
assert!(
slice.iter().all(|&b| b == 0xEE),
"data corruption for size {} in thread {}",
size,
tid
);
a.free(p);
}
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked during various-sizes test");
}
}
#[test]
fn hold_and_free_multiple_allocations() {
const NUM_THREADS: usize = 8;
const LIVE_COUNT: usize = 100;
const ROUNDS: usize = 50;
const SIZE: usize = 128;
unsafe {
alloc();
}
let barrier = Arc::new(Barrier::new(NUM_THREADS));
let handles: Vec<_> = (0..NUM_THREADS)
.map(|tid| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
unsafe {
let a = alloc();
let pattern = ((tid + 1) & 0xFF) as u8;
for _ in 0..ROUNDS {
let mut ptrs = Vec::with_capacity(LIVE_COUNT);
for _ in 0..LIVE_COUNT {
let p = a.malloc(SIZE);
assert!(!p.is_null());
ptr::write_bytes(p, pattern, SIZE);
ptrs.push(p);
}
for &p in &ptrs {
let slice = std::slice::from_raw_parts(p, SIZE);
assert!(
slice.iter().all(|&b| b == pattern),
"corruption in hold-and-free, thread {}",
tid
);
}
for p in ptrs {
a.free(p);
}
}
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked during hold-and-free test");
}
}
#[test]
fn realloc_under_contention() {
const NUM_THREADS: usize = 4;
const ITERATIONS: usize = 1_000;
unsafe {
alloc();
}
let barrier = Arc::new(Barrier::new(NUM_THREADS));
let handles: Vec<_> = (0..NUM_THREADS)
.map(|tid| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
unsafe {
let a = alloc();
let pattern = ((tid + 0x10) & 0xFF) as u8;
for _ in 0..ITERATIONS {
let initial_size = 32;
let p = a.malloc(initial_size);
assert!(!p.is_null());
ptr::write_bytes(p, pattern, initial_size);
let grown_size = 256;
let q = a.realloc(p, grown_size);
assert!(!q.is_null());
let slice = std::slice::from_raw_parts(q, initial_size);
assert!(
slice.iter().all(|&b| b == pattern),
"corruption after realloc grow, thread {}",
tid
);
a.free(q);
}
}
})
})
.collect();
for h in handles {
h.join()
.expect("thread panicked during realloc contention test");
}
}