use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
use crate::process::ProcessOwner;
pub(super) struct Section {
owner: ProcessOwner,
fd: Kept,
state: Mutex<Holders>,
released: Condvar,
}
#[derive(Default)]
struct Holders {
shared: usize,
exclusive: bool,
locking: bool,
waiting: usize,
}
impl Section {
pub(super) fn new(fd: OwnedFd) -> io::Result<Self> {
Ok(Self {
owner: ProcessOwner::current()?,
fd: Kept::new(fd)?,
state: Mutex::new(Holders::default()),
released: Condvar::new(),
})
}
pub(super) fn is_current(&self) -> bool {
self.owner.is_current()
}
fn holders(&self) -> MutexGuard<'_, Holders> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
fn wait<'a>(
&self,
mut holders: MutexGuard<'a, Holders>,
) -> MutexGuard<'a, Holders> {
holders.waiting += 1;
let mut holders =
self.released.wait(holders).unwrap_or_else(PoisonError::into_inner);
holders.waiting -= 1;
holders
}
fn wake(
&self,
holders: &Holders,
) {
if holders.waiting > 0 {
self.released.notify_all();
}
}
pub(super) fn acquire(
&self,
exclusive: bool,
reopen: impl FnOnce() -> io::Result<OwnedFd>,
) -> io::Result<()> {
let mut holders = self.holders();
loop {
if holders.exclusive || holders.locking {
holders = self.wait(holders);
continue;
}
if !exclusive && holders.shared > 0 {
holders.shared += 1;
return Ok(());
}
if exclusive && holders.shared > 0 {
holders = self.wait(holders);
continue;
}
break;
}
if exclusive {
holders.exclusive = true;
} else {
holders.locking = true;
}
drop(holders);
let locked = self.fd.refresh(reopen).and_then(|()| {
self.fd.flock(if exclusive { libc::LOCK_EX } else { libc::LOCK_SH })
});
let mut holders = self.holders();
if exclusive {
holders.exclusive = locked.is_ok();
} else {
holders.locking = false;
if locked.is_ok() {
holders.shared = 1;
}
}
self.wake(&holders);
locked
}
pub(super) fn release(
&self,
exclusive: bool,
) {
let mut holders = self.holders();
if exclusive {
let _ = self.fd.flock(libc::LOCK_UN);
holders.exclusive = false;
} else {
holders.shared -= 1;
if holders.shared == 0 {
let _ = self.fd.flock(libc::LOCK_UN);
}
}
self.wake(&holders);
}
}
struct Kept(Arc<AtomicI32>);
impl Kept {
fn new(fd: OwnedFd) -> io::Result<Self> {
let raw = fd.as_raw_fd();
let kept = Arc::new(AtomicI32::new(raw));
registry::insert(&kept)?;
std::mem::forget(fd);
Ok(Self(kept))
}
fn raw(&self) -> RawFd {
self.0.load(Ordering::Acquire)
}
fn flock(
&self,
operation: libc::c_int,
) -> io::Result<()> {
loop {
if unsafe { libc::flock(self.raw(), operation) } == 0 {
return Ok(());
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}
fn refresh(
&self,
reopen: impl FnOnce() -> io::Result<OwnedFd>,
) -> io::Result<()> {
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(self.raw(), &mut stat) } != 0 {
return Err(io::Error::last_os_error());
}
if stat.st_nlink > 0 {
return Ok(());
}
let fresh = reopen()?;
let old = self.0.swap(fresh.as_raw_fd(), Ordering::AcqRel);
std::mem::forget(fresh);
drop(unsafe { OwnedFd::from_raw_fd(old) });
Ok(())
}
}
impl Drop for Kept {
fn drop(&mut self) {
registry::remove(&self.0);
let raw = self.0.swap(-1, Ordering::AcqRel);
if raw >= 0 {
drop(unsafe { OwnedFd::from_raw_fd(raw) });
}
}
}
mod registry {
use super::*;
static BUSY: AtomicBool = AtomicBool::new(false);
static mut KEPT: Vec<*const AtomicI32> = Vec::new();
static HOOKED: std::sync::Once = std::sync::Once::new();
static HOOK_STATUS: AtomicI32 = AtomicI32::new(0);
fn enter() {
while BUSY
.compare_exchange_weak(
false,
true,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_err()
{
std::hint::spin_loop();
}
}
fn leave() {
BUSY.store(false, Ordering::Release);
}
extern "C" fn prepare() {
enter();
}
extern "C" fn parent() {
leave();
}
extern "C" fn child() {
let kept = unsafe { &*std::ptr::addr_of!(KEPT) };
for &fd in kept {
let raw = unsafe { &*fd }.swap(-1, Ordering::AcqRel);
if raw >= 0 {
unsafe { libc::close(raw) };
}
}
leave();
}
pub(super) fn insert(fd: &Arc<AtomicI32>) -> io::Result<()> {
HOOKED.call_once(|| {
let status = unsafe {
libc::pthread_atfork(Some(prepare), Some(parent), Some(child))
};
HOOK_STATUS.store(status, Ordering::Release);
});
let status = HOOK_STATUS.load(Ordering::Acquire);
if status != 0 {
return Err(io::Error::from_raw_os_error(status));
}
enter();
unsafe { (*std::ptr::addr_of_mut!(KEPT)).push(Arc::as_ptr(fd)) };
leave();
Ok(())
}
pub(super) fn remove(fd: &Arc<AtomicI32>) {
let target = Arc::as_ptr(fd);
enter();
unsafe {
(*std::ptr::addr_of_mut!(KEPT)).retain(|&entry| entry != target)
};
leave();
}
}