use std::any::Any;
use std::cell::RefCell;
use std::sync::{Mutex, TryLockError};
thread_local! {
static HELD: RefCell<Vec<Box<dyn Any>>> = const { RefCell::new(Vec::new()) };
}
struct Lock {
take: fn() -> Option<Box<dyn Any>>,
wait: fn(),
}
const LOCKS: [Lock; 2] = [Lock { take: take_hdf5, wait: wait_hdf5 }, Lock { take: take_checked, wait: wait_checked }];
fn take_hdf5() -> Option<Box<dyn Any>> {
crate::h5sys::LOCK.try_lock().map(|held| Box::new(held) as Box<dyn Any>)
}
fn wait_hdf5() {
drop(crate::h5sys::LOCK.lock());
}
fn take_checked() -> Option<Box<dyn Any>> {
free(super::ops::checked_files())
}
fn wait_checked() {
drop(super::ops::checked_files().lock());
}
fn free<T: 'static>(lock: &'static Mutex<T>) -> Option<Box<dyn Any>> {
match lock.try_lock() {
Ok(held) => Some(Box::new(held)),
Err(TryLockError::Poisoned(held)) => Some(Box::new(held.into_inner())),
Err(TryLockError::WouldBlock) => None,
}
}
#[cfg(unix)]
pub fn install() {
extern "C" fn before() {
prepare();
}
extern "C" fn parent() {
release();
}
extern "C" fn child() {
release_in_child();
}
static INSTALLED: std::sync::Once = std::sync::Once::new();
INSTALLED.call_once(|| {
unsafe { libc::pthread_atfork(Some(before), Some(parent), Some(child)) };
});
}
fn prepare() {
'all: loop {
let mut held = Vec::with_capacity(LOCKS.len());
for lock in &LOCKS {
match (lock.take)() {
Some(guard) => held.push(guard),
None => {
drop(held);
(lock.wait)();
continue 'all;
}
}
}
HELD.with(|h| *h.borrow_mut() = held);
return;
}
}
fn release() {
HELD.with(|h| h.borrow_mut().clear());
}
fn release_in_child() {
let key = unsafe { crate::h5sys::LOCK.raw() } as *const _ as usize;
unsafe { parking_lot_core::unpark_all(key, parking_lot_core::DEFAULT_UNPARK_TOKEN) };
release();
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
fn free_elsewhere() -> Vec<bool> {
std::thread::spawn(|| LOCKS.iter().map(|l| (l.take)().is_some()).collect()).join().unwrap()
}
#[test]
fn prepare_waits_for_a_held_lock_and_holds_every_lock_until_release() {
static RELEASED: AtomicBool = AtomicBool::new(false);
let (held_tx, held_rx) = mpsc::channel();
let holder = std::thread::spawn(move || {
let held = super::super::ops::checked_files().lock();
held_tx.send(()).unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
RELEASED.store(true, Ordering::SeqCst);
drop(held);
});
held_rx.recv().unwrap();
prepare();
assert!(RELEASED.load(Ordering::SeqCst), "prepare took a lock another thread held");
holder.join().unwrap();
assert_eq!(free_elsewhere(), vec![false, false], "a lock was free between prepare and release");
release();
std::thread::spawn(|| LOCKS.iter().for_each(|l| (l.wait)())).join().unwrap();
}
}