use std::{error, fmt, num, sync, sync::atomic};
use crate::Flavor;
static FORK_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
#[derive(Clone)]
pub struct Guard {
last_fork_count: usize,
}
impl fmt::Debug for Guard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("Guard")
.field("flavor()", &self.flavor())
.field("last_fork_count", &self.last_fork_count)
.finish()
}
}
impl Default for Guard {
#[track_caller]
fn default() -> Self {
Self::try_new().unwrap()
}
}
impl Guard {
pub const FLAVOR: Flavor = Flavor::Atfork;
pub fn try_new() -> Result<Self, AtforkError> {
static ATFORK_RESULT: sync::LazyLock<libc::c_int> =
sync::LazyLock::new(|| unsafe { libc::pthread_atfork(None, None, Some(fork_handler)) });
extern "C" fn fork_handler() {
FORK_COUNT.fetch_add(1, atomic::Ordering::Relaxed);
}
match *ATFORK_RESULT {
0 => Ok(Self {
last_fork_count: FORK_COUNT.load(atomic::Ordering::Relaxed),
}),
ret => Err(AtforkError(ret.try_into().unwrap())),
}
}
#[inline(always)]
pub fn detected_fork(&mut self) -> bool {
let current_fork_count = FORK_COUNT.load(atomic::Ordering::Relaxed);
if self.last_fork_count == current_fork_count {
false
} else {
self.set_fork_count(current_fork_count);
true
}
}
#[cold]
fn set_fork_count(&mut self, value: usize) {
self.last_fork_count = value;
}
pub fn flavor(&self) -> Flavor {
Self::FLAVOR.clone()
}
}
#[derive(Debug)]
pub struct AtforkError(num::NonZero<libc::c_int>);
impl AtforkError {
pub fn code(&self) -> num::NonZero<libc::c_int> {
self.0
}
}
impl fmt::Display for AtforkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "pthread_atfork() failed (code: {})", self.0)
}
}
impl error::Error for AtforkError {}