use std::sync::Arc;
use std::time::Duration;
use core::ops::Range;
use tokio::sync::Mutex;
use rangemap::map::RangeMap;
const TRY_WAIT_MICROS: u64 = 10;
#[derive(Clone)]
pub struct RangeLock {
inner: Arc<Mutex<RangeMap<u64, bool>>>,
aligned_size: u64,
aligned_shift: u32,
}
impl RangeLock {
pub fn new(aligned_size: u64) -> Self {
Self {
inner: Arc::new(Mutex::new(RangeMap::new())),
aligned_size: aligned_size,
aligned_shift: aligned_size.checked_ilog2().expect("failed to get aligned shift from aligned size"),
}
}
fn aligned_range(&self, range: &Range<u64>) -> Range<u64> {
Range {
start: range.start >> self.aligned_shift << self.aligned_shift,
end: (range.end + self.aligned_size - 1) >> self.aligned_shift << self.aligned_shift,
}
}
pub fn try_lock(&self, range: Range<u64>) -> bool {
let range = self.aligned_range(&range);
loop {
let Ok(mut lock) = self.inner.try_lock() else {
std::thread::sleep(Duration::from_micros(TRY_WAIT_MICROS));
continue;
};
let overlapped = lock.overlaps(&range);
if overlapped == true {
return false;
}
lock.insert(range, true);
return true;
}
}
pub fn try_unlock(&mut self, range: Range<u64>) {
let range = self.aligned_range(&range);
loop {
let Ok(mut lock) = self.inner.try_lock() else {
std::thread::sleep(Duration::from_micros(TRY_WAIT_MICROS));
continue;
};
lock.remove(range);
break;
}
}
pub async fn unlock(&mut self, range: Range<u64>) {
let mut lock = self.inner.lock().await;
let range = self.aligned_range(&range);
lock.remove(range);
}
pub fn is_locked(&self) -> bool {
match self.inner.try_lock() {
Ok(lock) => {
return !lock.is_empty();
},
Err(_) => {
return true;
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const BLOCK: u64 = 4096;
#[test]
fn new_lock_is_unlocked() {
let lock = RangeLock::new(BLOCK);
assert!(!lock.is_locked());
}
#[test]
fn lock_then_unlock() {
let mut lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..BLOCK));
assert!(lock.is_locked());
lock.try_unlock(0..BLOCK);
assert!(!lock.is_locked());
}
#[test]
fn overlapping_lock_fails() {
let lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..BLOCK));
assert!(!lock.try_lock(0..BLOCK));
}
#[test]
fn partial_overlap_fails() {
let lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..2 * BLOCK));
assert!(!lock.try_lock(BLOCK..3 * BLOCK));
}
#[test]
fn disjoint_ranges_both_succeed() {
let lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..BLOCK));
assert!(lock.try_lock(2 * BLOCK..3 * BLOCK));
}
#[test]
fn abutting_ranges_both_succeed() {
let lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..BLOCK));
assert!(lock.try_lock(BLOCK..2 * BLOCK));
}
#[test]
fn unlock_allows_relock() {
let mut lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..BLOCK));
assert!(!lock.try_lock(0..BLOCK));
lock.try_unlock(0..BLOCK);
assert!(lock.try_lock(0..BLOCK));
}
#[test]
fn aligned_range_rounds_out() {
let lock = RangeLock::new(BLOCK);
let expanded = lock.aligned_range(&(100..200));
assert_eq!(expanded, 0..BLOCK);
let expanded = lock.aligned_range(&(0..(BLOCK - 1)));
assert_eq!(expanded, 0..BLOCK);
let expanded = lock.aligned_range(&((BLOCK - 1)..(BLOCK + 1)));
assert_eq!(expanded, 0..2 * BLOCK);
let expanded = lock.aligned_range(&(BLOCK..3 * BLOCK));
assert_eq!(expanded, BLOCK..3 * BLOCK);
}
#[test]
fn lock_unaligned_conflicts_with_aligned() {
let lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(100..200));
assert!(!lock.try_lock(300..400));
assert!(lock.try_lock((BLOCK + 100)..(BLOCK + 200)));
}
#[test]
fn unlock_nonexistent_range_is_noop() {
let mut lock = RangeLock::new(BLOCK);
lock.try_unlock(0..BLOCK);
assert!(!lock.is_locked());
assert!(lock.try_lock(0..BLOCK));
}
#[tokio::test]
async fn async_unlock_also_releases() {
let mut lock = RangeLock::new(BLOCK);
assert!(lock.try_lock(0..BLOCK));
lock.unlock(0..BLOCK).await;
assert!(!lock.is_locked());
assert!(lock.try_lock(0..BLOCK));
}
#[test]
fn is_locked_reflects_state_across_clones() {
let lock = RangeLock::new(BLOCK);
let clone = lock.clone();
assert!(!lock.is_locked());
assert!(!clone.is_locked());
assert!(lock.try_lock(0..BLOCK));
assert!(clone.is_locked());
}
}