use std::{
hint,
sync::atomic::{AtomicU64, Ordering},
};
pub struct ShardedLogLockMap {
lock_map: AtomicU64,
}
impl Default for ShardedLogLockMap {
fn default() -> Self {
Self::new()
}
}
impl ShardedLogLockMap {
pub fn new() -> Self {
Self {
lock_map: AtomicU64::new(0),
}
}
pub fn lock_sublogs(&self, log_access_bitmap: u64) {
loop {
let current = self.lock_map.load(Ordering::Acquire);
if current & log_access_bitmap == 0 {
let new_map = current | log_access_bitmap;
if self
.lock_map
.compare_exchange_weak(current, new_map, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return;
}
}
hint::spin_loop();
}
}
pub fn unlock_sublogs(&self, mut log_access_bitmap: u64) {
debug_assert!(self.lock_map.load(Ordering::Relaxed) & log_access_bitmap > 0);
log_access_bitmap = !log_access_bitmap;
self
.lock_map
.fetch_and(log_access_bitmap, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use std::{
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
thread,
time::Duration,
};
use super::ShardedLogLockMap;
#[test]
fn lock_unlock_roundtrip() {
let map = ShardedLogLockMap::new();
map.lock_sublogs(0b101);
assert_eq!(map.lock_map.load(Ordering::Relaxed), 0b101);
map.unlock_sublogs(0b101);
assert_eq!(map.lock_map.load(Ordering::Relaxed), 0);
}
#[test]
fn contended_bits_block() {
let map = Arc::new(ShardedLogLockMap::new());
map.lock_sublogs(0b1);
let map2 = map.clone();
let acquired = Arc::new(AtomicU64::new(0));
let acquired2 = acquired.clone();
let handle = thread::spawn(move || {
map2.lock_sublogs(0b11);
acquired2.store(1, Ordering::Release);
});
thread::sleep(Duration::from_millis(5));
assert_eq!(acquired.load(Ordering::Acquire), 0);
map.unlock_sublogs(0b1);
handle.join().unwrap();
assert_eq!(acquired.load(Ordering::Acquire), 1);
}
}