use crate::{arena, base, huge, prof, tcache};
use core::sync::atomic::{AtomicU64, Ordering};
static STATE: AtomicU64 = AtomicU64::new(0);
const DONE: u64 = u64::MAX;
pub(crate) fn register() {
register_with(|| {});
}
#[inline(always)]
fn register_with(registered: impl FnOnce()) {
if STATE.load(Ordering::Acquire) == DONE {
return;
}
let me = u64::from(std::process::id());
let mut waiting_since = None;
loop {
match STATE.load(Ordering::Acquire) {
DONE => return,
s if s == 0 || s != me => {
if STATE
.compare_exchange(s, me, Ordering::Acquire, Ordering::Acquire)
.is_ok()
{
let ok =
unsafe { libc::pthread_atfork(Some(prepare), Some(parent), Some(child)) }
== 0;
if ok {
registered();
}
STATE.store(if ok { DONE } else { 0 }, Ordering::Release);
return;
}
}
_ => {
let since = *waiting_since.get_or_insert_with(std::time::Instant::now);
if since.elapsed() > std::time::Duration::from_secs(1) {
let _ = STATE.compare_exchange(me, 0, Ordering::AcqRel, Ordering::Acquire);
}
std::thread::yield_now();
}
}
}
}
unsafe extern "C" fn prepare() {
prof::fork_lock_dump();
arena::fork_lock();
huge::fork_lock();
prof::fork_lock();
tcache::fork_lock();
base::fork_lock();
}
unsafe fn unlock_all() {
unsafe {
base::fork_unlock();
tcache::fork_unlock();
prof::fork_unlock();
huge::fork_unlock();
arena::fork_unlock();
prof::fork_unlock_dump();
}
}
unsafe extern "C" fn parent() {
unsafe { unlock_all() };
}
unsafe extern "C" fn child() {
STATE.store(DONE, Ordering::Release);
unsafe {
unlock_all();
let t = tcache::current();
arena::fork_child(if t.is_null() {
core::ptr::null_mut()
} else {
(*t).arena
});
}
crate::background::fork_child();
}
#[cfg(test)]
mod tests {
use super::{STATE, register, register_with};
use core::sync::atomic::Ordering;
use std::time::{Duration, Instant};
fn wait(pid: libc::pid_t) -> i32 {
let deadline = Instant::now() + Duration::from_secs(10);
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 2;
}
std::thread::sleep(Duration::from_millis(5));
}
if libc::WIFEXITED(status) {
libc::WEXITSTATUS(status)
} else {
3
}
}
#[test]
fn stale_claim_is_taken_over() {
let pid = unsafe { libc::fork() };
assert!(pid >= 0);
if pid == 0 {
STATE.store(u64::from(std::process::id()), Ordering::Release);
register();
let done = STATE.load(Ordering::Acquire) == super::DONE;
unsafe { libc::_exit(i32::from(!done)) };
}
assert_eq!(wait(pid), 0, "register() waited on the stale claim");
}
#[test]
fn fork_between_registration_and_its_record() {
assert_eq!(STATE.load(Ordering::Acquire), 0, "nothing registered yet");
let mut code = -1;
register_with(|| {
let pid = unsafe { libc::fork() };
assert!(pid >= 0);
if pid == 0 {
register();
let pid = unsafe { libc::fork() };
if pid == 0 {
unsafe { libc::_exit(0) };
}
unsafe { libc::_exit(wait(pid)) };
}
code = wait(pid);
});
assert_eq!(code, 0, "the child's next fork did not finish");
}
}