use std::{
ops::{Deref, Not as _},
sync::atomic::{AtomicBool, Ordering},
thread::park_timeout,
time::Duration,
};
pub struct Lock<T: Deref<Target = AtomicBool>>(T);
impl<T> Lock<T>
where
T: Deref<Target = AtomicBool>,
{
pub fn try_get(locked: T) -> Option<Self> {
locked
.deref()
.swap(true, Ordering::AcqRel)
.not()
.then(|| Lock(locked))
}
pub fn get(locked: T, timeout: Duration) -> Self {
let steps = 32;
for n in 0..steps {
if !locked.deref().swap(true, Ordering::AcqRel) {
return Lock(locked);
}
let timeout = timeout.as_secs_f32() * 1024.0 * 1024.0 * 1024.0;
let base = timeout.powf(1.0 / steps as f32);
let duration = Duration::from_nanos(base.powi(n) as u64);
park_timeout(duration);
}
unreachable!("Failed to get a lock");
}
}
impl<T: Deref<Target = AtomicBool>> Drop for Lock<T> {
fn drop(&mut self) {
self.0.deref().store(false, Ordering::Release);
}
}
#[test]
#[should_panic]
fn times_out() {
let busy = true.into();
Lock::get(&busy, Duration::from_millis(10));
}
#[test]
fn locks_unlocks() {
let busy = AtomicBool::new(false);
let lock = Lock::get(&busy, Duration::from_millis(10));
assert!(busy.load(Ordering::Relaxed));
drop(lock);
assert!(!busy.load(Ordering::Relaxed));
}