use core::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TryLockErr {
Removed,
Retry,
}
#[derive(Debug, Default)]
pub struct VersionLock {
version: AtomicUsize,
}
impl VersionLock {
pub const fn new() -> Self {
Self {
version: AtomicUsize::new(0),
}
}
#[inline]
fn version_generation(&self, version: usize) -> usize {
if version & 2 == 0 {
version
} else {
version + 2
}
}
#[inline]
pub fn try_lock(&self) -> Result<usize, TryLockErr> {
let version = self.version.load(Ordering::Relaxed);
if version & 1 == 1 {
return Err(TryLockErr::Removed);
}
let version = self.version_generation(version);
match self.version.compare_exchange(
version,
version + 2,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(v) => Ok(v),
Err(v) => {
if v & 1 == 1 {
Err(TryLockErr::Removed)
} else {
Err(TryLockErr::Retry)
}
}
}
}
#[inline]
pub fn lock(&self) -> Result<usize, TryLockErr> {
let backoff = crossbeam_utils::Backoff::new();
let version = self.version.load(Ordering::Relaxed);
if version & 1 == 1 {
return Err(TryLockErr::Removed);
}
let mut version = self.version_generation(version);
while let Err(v) = self.version.compare_exchange_weak(
version,
version + 2,
Ordering::Acquire,
Ordering::Relaxed,
) {
if v & 1 == 1 {
return Err(TryLockErr::Removed);
}
version = self.version_generation(v);
backoff.snooze();
}
Ok(version)
}
#[inline]
pub fn unlock(&self) {
let version = self.version.load(Ordering::Relaxed);
self.version.store(version + 2, Ordering::Release);
}
#[inline]
pub fn unlock_remove(&self) {
let version = self.version.load(Ordering::Relaxed);
self.version.store(version + 3, Ordering::Release);
}
#[inline]
pub fn is_removed(&self) -> bool {
self.version.load(Ordering::Relaxed) & 1 == 1
}
}
#[cfg(test)]
mod tests {
#[test]
fn version_lock_test() {
let lock = super::VersionLock::new();
assert_eq!(lock.try_lock(), Ok(0));
assert_eq!(lock.try_lock(), Err(super::TryLockErr::Retry));
lock.unlock();
assert_eq!(lock.try_lock(), Ok(4));
lock.unlock_remove();
assert_eq!(lock.try_lock(), Err(super::TryLockErr::Removed));
assert!(lock.is_removed());
}
}