use std::{
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicU64, Ordering::Relaxed},
},
thread,
time::Duration,
};
use crate::{
RwLockBell,
tests::hooks::{self, Gate, HookPoint, TestGuard},
};
#[test]
fn test_in_flight_try_write_or_collected_by_write_guard_drain() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let called = Arc::new(AtomicBool::new(false));
let gate = Gate::new();
let g2 = gate.clone();
hooks::set(HookPoint::TryWriteOrBeforeAcquire, move || g2.wait());
let lock_b = lock.clone();
let called_b = called.clone();
let t_b = thread::spawn(move || {
let _ = lock_b.try_write_or(move || called_b.store(true, Relaxed));
});
gate.wait_for_arrival(); hooks::clear(HookPoint::TryWriteOrBeforeAcquire);
let guard = lock.write();
gate.open();
drop(guard);
t_b.join().unwrap();
assert!(
called.load(Relaxed),
"callback must fire after drain completes"
);
}
#[test]
fn test_try_write_or_during_drain_eventually_proceeds() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let b_handle: Arc<Mutex<Option<thread::JoinHandle<_>>>> = Arc::new(Mutex::new(None));
let b_handle2 = b_handle.clone();
let b_proceeded = Arc::new(AtomicBool::new(false));
let b_proceeded2 = b_proceeded.clone();
let lock_b = lock.clone();
hooks::set(HookPoint::DrainAfterWriteLockRelease, move || {
hooks::clear(HookPoint::DrainAfterWriteLockRelease); let bp2 = b_proceeded2.clone();
let lb2 = lock_b.clone();
let handle = thread::spawn(move || {
let guard = lb2.try_write_or(|| {});
bp2.store(true, Relaxed);
drop(guard);
});
*b_handle2.lock().unwrap() = Some(handle);
});
let gate_hold = Gate::new();
let gh2 = gate_hold.clone();
let lock_a = lock.clone();
let t_a = thread::spawn(move || {
let guard = lock_a.write();
assert!(lock_a.try_write_or(|| {}).is_none());
gh2.wait(); drop(guard); });
gate_hold.wait_for_arrival(); gate_hold.open();
t_a.join().unwrap();
let handle = b_handle
.lock()
.unwrap()
.take()
.expect("B must have been spawned");
handle.join().unwrap();
assert!(b_proceeded.load(Relaxed), "B must complete after drain");
}
#[test]
fn test_in_flight_try_write_or_during_last_read_guard_drop() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let called = Arc::new(AtomicU64::new(0));
let gate = Gate::new();
let g2 = gate.clone();
hooks::set(HookPoint::TryWriteOrBeforeAcquire, move || g2.wait());
let lock_b = lock.clone();
let called_b = called.clone();
let t_b = thread::spawn(move || {
if lock_b
.try_write_or(move || {
called_b.fetch_add(1, Relaxed);
})
.is_none()
{
}
});
gate.wait_for_arrival(); hooks::clear(HookPoint::TryWriteOrBeforeAcquire);
let r = lock.read();
let called2 = called.clone();
assert!(
lock.try_write_or(move || {
called2.fetch_add(1, Relaxed);
})
.is_none()
);
gate.open(); drop(r);
t_b.join().unwrap();
let n = called.load(Relaxed);
assert!(n >= 1, "read-guard callback must have fired");
assert!(n <= 2, "no callback should fire more than once");
}
#[test]
fn test_read_guard_drop_atomicity() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let fired = Arc::new(AtomicU64::new(0));
let r = lock.read();
let f1 = fired.clone();
assert!(
lock.try_write_or(move || {
f1.fetch_add(1, Relaxed);
})
.is_none()
);
let gate = Gate::new();
let g2 = gate.clone();
hooks::set(HookPoint::ReadGuardAfterRelease, move || g2.wait());
let g3 = gate.clone();
let releaser = thread::spawn(move || {
g3.wait_for_arrival(); g3.open(); });
drop(r);
releaser.join().unwrap();
assert_eq!(fired.load(Relaxed), 1, "callback must fire exactly once");
}
#[test]
fn test_while_dropping_loop_is_entered() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let gate_drain = Gate::new();
let gd2 = gate_drain.clone();
hooks::set(HookPoint::DrainAfterWriteLockRelease, move || {
hooks::clear(HookPoint::DrainAfterWriteLockRelease); gd2.wait(); });
let gate_in_dropping = Gate::new();
let gid2 = gate_in_dropping.clone();
hooks::set(HookPoint::TryWriteOrWhileDropping, move || {
hooks::clear(HookPoint::TryWriteOrWhileDropping); gid2.signal(); });
let lock_a = lock.clone();
let gate_a = Gate::new();
let ga2 = gate_a.clone();
let t_a = thread::spawn(move || {
let guard = lock_a.write();
assert!(lock_a.try_write_or(|| {}).is_none());
ga2.wait(); drop(guard); });
gate_a.wait_for_arrival(); gate_a.open();
let lock_b = lock.clone();
let b_proceeded = Arc::new(AtomicBool::new(false));
let bp2 = b_proceeded.clone();
let t_b = thread::spawn(move || {
let _ = lock_b.try_write_or(|| {});
bp2.store(true, Relaxed);
});
gate_in_dropping.wait_for_arrival();
gate_drain.open();
t_a.join().unwrap();
t_b.join().unwrap();
assert!(
b_proceeded.load(Relaxed),
"B must complete after dropping is reset"
);
}
#[test]
fn test_write_guard_locking_zero_wait_is_entered() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let called = Arc::new(AtomicBool::new(false));
let gate_b = Gate::new();
let gb2 = gate_b.clone();
hooks::set(HookPoint::TryWriteOrBeforeAcquire, move || gb2.wait());
let gate_dropping = Gate::new();
let gd2 = gate_dropping.clone();
hooks::set(HookPoint::WriteGuardAfterSettingDropping, move || {
hooks::clear(HookPoint::WriteGuardAfterSettingDropping); gd2.signal(); });
let lock_b = lock.clone();
let called_b = called.clone();
let t_b = thread::spawn(move || {
let _ = lock_b.try_write_or(move || called_b.store(true, Relaxed));
});
gate_b.wait_for_arrival(); hooks::clear(HookPoint::TryWriteOrBeforeAcquire);
let guard = lock.write();
let gb3 = gate_b.clone();
let orchestrator = thread::spawn(move || {
gate_dropping.wait_for_arrival();
gb3.open();
});
drop(guard);
orchestrator.join().unwrap();
t_b.join().unwrap();
assert!(called.load(Relaxed), "callback must fire after drain");
}
#[test]
fn test_callbacks_run_after_dropping_is_reset() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let callback_ran = Arc::new(AtomicBool::new(false));
let cr2 = callback_ran.clone();
let lock2 = lock.clone();
hooks::set(HookPoint::DrainBeforeCallbacks, move || {
hooks::clear(HookPoint::DrainBeforeCallbacks); let guard = lock2.try_write_or(|| {});
assert!(
guard.is_some(),
"lock must be acquirable when dropping=false"
);
});
let lock3 = lock.clone();
let guard = lock.write();
assert!(
lock.try_write_or(move || {
let _ = lock3.try_write_or(|| {});
cr2.store(true, Relaxed);
})
.is_none()
);
drop(guard);
assert!(callback_ran.load(Relaxed));
}
#[test]
fn regression_last_reader_drain_waits_for_in_flight_locking() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let fired = Arc::new(AtomicBool::new(false));
let gate_factory = Gate::new();
let gate_drain = Gate::new();
let gd2 = gate_drain.clone();
hooks::set(HookPoint::ReadGuardAfterSettingDropping, move || {
hooks::clear(HookPoint::ReadGuardAfterSettingDropping); gd2.signal();
});
let r = lock.read();
let lock_t = lock.clone();
let fired_t = fired.clone();
let gf2 = gate_factory.clone();
let t = thread::spawn(move || {
let res = lock_t.try_write_or_else(move || {
gf2.wait(); move || fired_t.store(true, Relaxed)
});
assert!(res.is_none(), "read guard was held at try_write time");
});
gate_factory.wait_for_arrival();
let gf3 = gate_factory.clone();
let opener = thread::spawn(move || {
gate_drain.wait_for_arrival();
gf3.open();
});
let watchdog_stop = Arc::new(AtomicBool::new(false));
let watchdog_stop2 = watchdog_stop.clone();
let watchdog = thread::spawn(move || {
for _ in 0..50 {
if watchdog_stop2.load(Relaxed) {
return;
}
thread::sleep(Duration::from_millis(100));
}
eprintln!("[regression_last_reader_drain_waits_for_in_flight_locking] WATCHDOG fired");
std::process::abort();
});
drop(r);
opener.join().unwrap();
t.join().unwrap();
assert!(
fired.load(Relaxed),
"callback pushed by the in-flight call must be collected by the read drain"
);
watchdog_stop.store(true, Relaxed);
watchdog.join().unwrap();
}
#[test]
fn regression_double_drain_no_deadlock() {
let _g = TestGuard::acquire();
let lock = Arc::new(RwLockBell::new(0u64));
let r = lock.read();
let q_fired = Arc::new(AtomicBool::new(false));
let qf2 = q_fired.clone();
assert!(
lock.try_write_or(move || qf2.store(true, Relaxed))
.is_none()
);
let gate_t = Gate::new();
let gt2 = gate_t.clone();
hooks::set(HookPoint::TryWriteOrBeforeAcquire, move || gt2.wait());
let lock_t = lock.clone();
let t_fired = Arc::new(AtomicBool::new(false));
let tf2 = t_fired.clone();
let t_handle = thread::spawn(move || {
let _ = lock_t.try_write_or(move || tf2.store(true, Relaxed));
});
gate_t.wait_for_arrival();
hooks::clear(HookPoint::TryWriteOrBeforeAcquire);
let r_set = Gate::new();
let r_set2 = r_set.clone();
hooks::set(HookPoint::ReadGuardAfterSettingDropping, move || {
hooks::clear(HookPoint::ReadGuardAfterSettingDropping);
r_set2.signal();
});
let w_set = Gate::new();
let w_set2 = w_set.clone();
hooks::set(HookPoint::WriteGuardAfterSettingDropping, move || {
hooks::clear(HookPoint::WriteGuardAfterSettingDropping);
w_set2.signal();
});
let lock_orc = lock.clone();
let gate_t_open = gate_t.clone();
let r_set_arrival = r_set.clone();
let w_set_arrival = w_set.clone();
let orchestrator = thread::spawn(move || {
r_set_arrival.wait_for_arrival();
let lock_w = lock_orc.clone();
let w_handle = thread::spawn(move || {
let w = lock_w.write();
drop(w);
});
w_set_arrival.wait_for_arrival();
gate_t_open.open();
w_handle.join().unwrap();
});
let watchdog_stop = Arc::new(AtomicBool::new(false));
let watchdog_stop2 = watchdog_stop.clone();
let watchdog = thread::spawn(move || {
for _ in 0..50 {
if watchdog_stop2.load(Relaxed) {
return;
}
thread::sleep(Duration::from_millis(100));
}
eprintln!("[regression_double_drain_no_deadlock] WATCHDOG fired");
std::process::abort();
});
drop(r);
t_handle.join().unwrap();
orchestrator.join().unwrap();
assert!(q_fired.load(Relaxed), "Q's callback must fire");
assert!(t_fired.load(Relaxed), "T's callback must fire");
watchdog_stop.store(true, Relaxed);
watchdog.join().unwrap();
}