use super::TrackingGuard;
use std::cell::Cell;
use std::sync::{Mutex, MutexGuard};
static GATE: Mutex<()> = Mutex::new(());
thread_local! {
static ENTERED: Cell<bool> = const { Cell::new(false) };
}
pub(crate) struct CaptureGate {
lock: Option<MutexGuard<'static, ()>>,
_suppression: Option<TrackingGuard>,
}
pub(crate) fn acquire(_ptr: usize) -> CaptureGate {
let suppression = TrackingGuard::acquire();
let lock = if ENTERED.with(Cell::get) {
None
} else {
let lock = GATE.lock().unwrap_or_else(|error| error.into_inner());
ENTERED.with(|entered| entered.set(true));
Some(lock)
};
CaptureGate {
lock,
_suppression: suppression,
}
}
impl Drop for CaptureGate {
fn drop(&mut self) {
if self.lock.is_some() {
ENTERED.with(|entered| entered.set(false));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn different_addresses_share_exclusion() {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Barrier;
let active = AtomicUsize::new(0);
let start = Barrier::new(50);
std::thread::scope(|scope| {
for address in 0..50 {
let active = &active;
let start = &start;
scope.spawn(move || {
start.wait();
for _ in 0..20 {
let _gate = acquire(0x1000 + address * 64);
let previous = active.fetch_add(1, Ordering::SeqCst);
std::thread::yield_now();
active.fetch_sub(1, Ordering::SeqCst);
assert_eq!(previous, 0, "capture sections must never overlap");
}
});
}
});
}
#[test]
fn nested_acquisition_reuses_the_outer_gate() {
ENTERED.with(|entered| entered.set(false));
{
let outer = acquire(0x1000);
assert!(outer.lock.is_some(), "the first gate must take the lock");
{
let inner = acquire(0x8000);
assert!(
inner.lock.is_none(),
"a nested gate must not take the lock again"
);
}
assert!(
ENTERED.with(Cell::get),
"the nested gate must not release the outer lock"
);
}
assert!(
!ENTERED.with(Cell::get),
"dropping the outer gate must release the marker"
);
}
}