use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use parking_lot::Condvar;
use parking_lot::Mutex;
const EXCLUSIVE_SIGNPOST: u64 = !0;
#[derive(Debug)]
pub struct Lockout {
count: AtomicU64,
lockout_mutex: Mutex<()>,
lockout_condvar: Condvar,
}
impl Lockout {
pub fn new() -> Self {
Self {
count: AtomicU64::new(0),
lockout_mutex: Mutex::new(()),
lockout_condvar: Condvar::new(),
}
}
pub fn get_warrant<P: LockoutProvider>(provider: P) -> Warrant<P> {
let lockout = provider.provide();
let starting_count = lockout.count.load(Ordering::SeqCst);
if starting_count != EXCLUSIVE_SIGNPOST {
let swap_result = lockout.count.compare_exchange(
starting_count,
starting_count + 1,
Ordering::SeqCst,
Ordering::SeqCst,
);
if swap_result.is_ok() {
return Warrant { provider };
}
}
let mut guard = lockout.lockout_mutex.lock();
loop {
let value = lockout.count.load(Ordering::SeqCst);
if value == EXCLUSIVE_SIGNPOST {
lockout.lockout_condvar.wait(&mut guard);
} else {
let swap_result = lockout.count.compare_exchange(
value,
value + 1,
Ordering::SeqCst,
Ordering::SeqCst,
);
if swap_result.is_ok() {
drop(guard);
return Warrant { provider };
}
}
}
}
pub fn get_exclusive_warrant<P: LockoutProvider>(provider: P) -> Option<ExclusiveWarrant<P>> {
let lockout = provider.provide();
let swap_result = lockout.count.compare_exchange(
0,
EXCLUSIVE_SIGNPOST,
Ordering::SeqCst,
Ordering::SeqCst,
);
if swap_result.is_ok() {
Some(ExclusiveWarrant { provider })
} else {
None
}
}
}
#[derive(Debug)]
pub struct Warrant<P: LockoutProvider> {
provider: P,
}
impl<P: LockoutProvider> Drop for Warrant<P> {
fn drop(&mut self) {
loop {
let lockout = self.provider.provide();
let count = lockout.count.load(Ordering::SeqCst);
assert!(count > 0 && count != EXCLUSIVE_SIGNPOST);
let swap_result = lockout.count.compare_exchange(
count,
count - 1,
Ordering::SeqCst,
Ordering::SeqCst,
);
if swap_result.is_ok() {
return;
}
}
}
}
#[derive(Debug)]
pub struct ExclusiveWarrant<P: LockoutProvider> {
provider: P,
}
impl<P: LockoutProvider> Drop for ExclusiveWarrant<P> {
fn drop(&mut self) {
let lockout = self.provider.provide();
let _guard = lockout.lockout_mutex.lock();
let swap_result = lockout.count.compare_exchange(
EXCLUSIVE_SIGNPOST,
0,
Ordering::SeqCst,
Ordering::SeqCst,
);
assert!(swap_result.is_ok());
lockout.lockout_condvar.notify_all();
}
}
pub trait LockoutProvider {
fn provide(&self) -> &Lockout;
}
impl LockoutProvider for Arc<Lockout> {
fn provide(&self) -> &Lockout {
&*self
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use super::Lockout;
#[test]
fn warrant_prevents_exclusive_warrant() {
let lockout = Arc::new(Lockout::new());
let _warrant = Lockout::get_warrant(lockout.clone());
let exclusive_warrant_option = Lockout::get_exclusive_warrant(lockout);
assert!(exclusive_warrant_option.is_none());
}
#[test]
fn exclusive_warrant_works_by_itself() {
let lockout = Arc::new(Lockout::new());
let exclusive_warrant_option = Lockout::get_exclusive_warrant(lockout);
assert!(exclusive_warrant_option.is_some());
}
#[test]
fn multiple_warrants() {
let lockout = Arc::new(Lockout::new());
let _warrant_1 = Lockout::get_warrant(lockout.clone());
let _warrant_2 = Lockout::get_warrant(lockout);
}
}