use core::sync::atomic::{AtomicUsize, Ordering};
const UNIQUE_BIT: usize = !(usize::MAX >> 1);
const COUNTER_MASK: usize = usize::MAX >> 1;
pub struct AtomicBorrow(AtomicUsize);
impl AtomicBorrow {
pub const fn new() -> Self {
Self(AtomicUsize::new(0))
}
pub fn borrow(&self) -> bool {
let prev_value = self.0.fetch_add(1, Ordering::Acquire);
if prev_value & COUNTER_MASK == COUNTER_MASK {
core::panic!("immutable borrow counter overflowed")
}
if prev_value & UNIQUE_BIT != 0 {
self.0.fetch_sub(1, Ordering::Release);
false
} else {
true
}
}
pub fn borrow_mut(&self) -> bool {
self.0
.compare_exchange(0, UNIQUE_BIT, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
pub fn release(&self) {
let value = self.0.fetch_sub(1, Ordering::Release);
debug_assert!(value != 0, "unbalanced release");
debug_assert!(value & UNIQUE_BIT == 0, "shared release of unique borrow");
}
pub fn release_mut(&self) {
let value = self.0.fetch_and(!UNIQUE_BIT, Ordering::Release);
debug_assert_ne!(value & UNIQUE_BIT, 0, "unique release of shared borrow");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "immutable borrow counter overflowed")]
fn test_borrow_counter_overflow() {
let counter = AtomicBorrow(AtomicUsize::new(COUNTER_MASK));
counter.borrow();
}
#[test]
#[should_panic(expected = "immutable borrow counter overflowed")]
fn test_mut_borrow_counter_overflow() {
let counter = AtomicBorrow(AtomicUsize::new(COUNTER_MASK | UNIQUE_BIT));
counter.borrow();
}
#[test]
fn test_borrow() {
let counter = AtomicBorrow::new();
assert!(counter.borrow());
assert!(counter.borrow());
assert!(!counter.borrow_mut());
counter.release();
counter.release();
assert!(counter.borrow_mut());
assert!(!counter.borrow());
counter.release_mut();
assert!(counter.borrow());
}
}