#![cfg(target_os = "linux")]
use std::alloc::{GlobalAlloc, Layout};
use std::time::{Duration, Instant};
use stoolap_jemalloc::Jemalloc;
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc::new();
fn vm_size() -> libc::rlim_t {
let st = std::fs::read_to_string("/proc/self/status").unwrap();
let kb: libc::rlim_t = st
.lines()
.find(|l| l.starts_with("VmSize:"))
.and_then(|l| l.split_whitespace().nth(1)?.parse().ok())
.unwrap();
kb << 10
}
fn in_child(f: fn() -> bool) -> i32 {
let pid = unsafe { libc::fork() };
assert!(pid >= 0);
if pid == 0 {
unsafe { libc::_exit(i32::from(!f())) };
}
let deadline = Instant::now() + Duration::from_secs(30);
let mut status = 0;
while unsafe { libc::waitpid(pid, &raw mut status, libc::WNOHANG) } != pid {
if Instant::now() > deadline {
unsafe { libc::kill(pid, libc::SIGKILL) };
unsafe { libc::waitpid(pid, &raw mut status, 0) };
return -1;
}
std::thread::sleep(Duration::from_millis(5));
}
if libc::WIFEXITED(status) {
libc::WEXITSTATUS(status)
} else {
-2
}
}
#[test]
fn cached_mappings_make_room() {
let code = in_child(|| unsafe {
let freed = Layout::from_size_align(12 << 20, 8).unwrap();
let a = GLOBAL.alloc(freed);
let b = GLOBAL.alloc(freed);
GLOBAL.dealloc(a, freed);
GLOBAL.dealloc(b, freed);
let limit = vm_size() + (8 << 20);
let rl = libc::rlimit {
rlim_cur: limit,
rlim_max: libc::RLIM_INFINITY,
};
assert_eq!(libc::setrlimit(libc::RLIMIT_AS, &raw const rl), 0);
let want = Layout::from_size_align(20 << 20, 8).unwrap();
let p = GLOBAL.alloc(want);
if p.is_null() {
return false;
}
p.write(1);
GLOBAL.dealloc(p, want);
true
});
assert_eq!(
code, 0,
"a 20 MiB allocation failed while 24 MiB sat in the cache"
);
}
#[test]
fn null_past_the_limit_then_recovery() {
let code = in_child(|| unsafe {
let limit = vm_size() + (64 << 20);
let rl = libc::rlimit {
rlim_cur: limit,
rlim_max: libc::RLIM_INFINITY,
};
assert_eq!(libc::setrlimit(libc::RLIMIT_AS, &raw const rl), 0);
let mut live: [(*mut u8, Layout); 512] =
[(core::ptr::null_mut(), Layout::new::<u8>()); 512];
let mut n = 0;
let mut nulls = 0;
let sizes = [48usize, 3000, 40_000, 300_000, 3 << 20];
for i in 0..4096 {
let l = Layout::from_size_align(sizes[i % sizes.len()], 8).unwrap();
let p = GLOBAL.alloc(l);
if p.is_null() {
nulls += 1;
continue;
}
p.write(i as u8);
if n < live.len() {
live[n] = (p, l);
n += 1;
} else {
GLOBAL.dealloc(p, l);
}
}
for &(p, l) in &live[..n] {
GLOBAL.dealloc(p, l);
}
let rl = libc::rlimit {
rlim_cur: libc::RLIM_INFINITY,
rlim_max: libc::RLIM_INFINITY,
};
libc::setrlimit(libc::RLIMIT_AS, &raw const rl);
let l = Layout::from_size_align(5 << 20, 8).unwrap();
let p = GLOBAL.alloc(l);
let back = !p.is_null();
if back {
GLOBAL.dealloc(p, l);
}
nulls > 0 && back
});
assert_eq!(code, 0, "no null past the limit, or no recovery after it");
}