#![cfg(unix)]
use std::hint::black_box;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};
use stoolap_jemalloc::{Jemalloc, background, prof};
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc::new();
static STOP: AtomicBool = AtomicBool::new(false);
fn churn(seed: usize) {
let mut kept: Vec<Vec<u8>> = Vec::new();
let mut i = seed as u64;
while !STOP.load(Ordering::Relaxed) {
i = i.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
let size = match i % 100 {
0 => 2 << 20,
1..=10 => 100_000,
_ => 16 + (i % 4000) as usize,
};
kept.push(black_box(vec![1u8; size]));
if kept.len() > 64 {
kept.swap_remove((i % 64) as usize % kept.len());
}
if i.is_multiple_of(1000) {
stoolap_jemalloc::purge();
}
}
}
fn child_work(dump: bool) -> ! {
let mut v: Vec<Vec<u8>> = (0..2000).map(|i| vec![2u8; 16 + i * 37 % 5000]).collect();
v.push(vec![3u8; 300_000]);
v.push(vec![4u8; 3 << 20]);
let ok = v
.iter()
.all(|b| b.iter().step_by(61).chain(b.last()).all(|&x| x == b[0]));
drop(v);
stoolap_jemalloc::purge();
let ok = ok && (!dump || prof::dump_pprof().is_ok());
let ok = ok && !background::is_running();
unsafe { libc::_exit(i32::from(!ok)) }
}
#[test]
fn fork_while_other_threads_allocate() {
prof::set_sample_interval(4096);
prof::activate();
background::start();
let workers: Vec<_> = (0..4).map(|t| thread::spawn(move || churn(t))).collect();
let start = Instant::now();
let mut round = 0;
while round < 20 || (round < 200 && start.elapsed() < Duration::from_secs(3)) {
let pid = unsafe { libc::fork() };
assert!(pid >= 0, "fork failed");
if pid == 0 {
child_work(round % 10 == 0);
}
let deadline = Instant::now() + Duration::from_secs(10);
let mut status = 0;
loop {
let done = unsafe { libc::waitpid(pid, &raw mut status, libc::WNOHANG) };
if done == pid {
break;
}
if Instant::now() > deadline {
unsafe { libc::kill(pid, libc::SIGKILL) };
unsafe { libc::waitpid(pid, &raw mut status, 0) };
STOP.store(true, Ordering::Relaxed);
panic!("child {round} deadlocked after fork");
}
thread::sleep(Duration::from_millis(1));
}
assert!(
libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0,
"child {round} failed: {status}"
);
round += 1;
}
STOP.store(true, Ordering::Relaxed);
for w in workers {
w.join().unwrap();
}
}