use crate::vecparts::VecParts;
use std::{
cell::UnsafeCell,
marker::PhantomData,
mem::size_of,
ops::{Index, IndexMut, Range},
rc::Rc,
slice,
sync::{
atomic::{AtomicU32, Ordering},
TryLockError, TryLockResult,
},
};
#[derive(Debug)]
pub struct RepVecRangeLock<T> {
slice_len: usize,
cycle_len: usize,
cycle_num_elems: usize,
locked_offsets: Vec<AtomicU32>,
data: UnsafeCell<VecParts<T>>,
}
unsafe impl<T> Sync for RepVecRangeLock<T> where T: Send {}
impl<'a, T> RepVecRangeLock<T> {
pub fn new(data: Vec<T>, slice_len: usize, cycle_len: usize) -> RepVecRangeLock<T> {
if slice_len == 0 {
panic!("slice_len must not be 0.");
}
if cycle_len == 0 || cycle_len > usize::MAX - 31 {
panic!("cycle_len out of range.");
}
let Some(cycle_num_elems) = cycle_len.checked_mul(slice_len) else {
panic!("Repeat cycle overflow.");
};
let num = cycle_len.div_ceil(32);
let mut locked_offsets = Vec::with_capacity(num);
locked_offsets.resize_with(num, || AtomicU32::new(0));
let data = UnsafeCell::new(data.into());
RepVecRangeLock {
slice_len,
cycle_len,
cycle_num_elems,
locked_offsets,
data,
}
}
#[inline]
pub fn data_len(&self) -> usize {
unsafe { (*self.data.get()).len() }
}
#[inline]
pub fn into_inner(self) -> Vec<T> {
debug_assert!(self
.locked_offsets
.iter()
.all(|x| x.load(Ordering::Acquire) == 0));
self.data.into_inner().into()
}
#[inline]
pub fn try_lock(&'a self, cycle_offset: usize) -> TryLockResult<RepVecRangeLockGuard<'a, T>> {
if cycle_offset >= self.cycle_len {
panic!("Invalid cycle_offset. It must be 0 <= cycle_offset < cycle_len.");
}
let idx = cycle_offset / 32;
let mask = 1 << (cycle_offset % 32);
let locked_offsets = unsafe { self.locked_offsets.get_unchecked(idx) };
let prev = locked_offsets.fetch_or(mask, Ordering::AcqRel);
if prev & mask == 0 {
let cycle_offset_slices = self.slice_len * cycle_offset;
TryLockResult::Ok(RepVecRangeLockGuard::new(
self,
cycle_offset,
cycle_offset_slices,
))
} else {
TryLockResult::Err(TryLockError::WouldBlock)
}
}
#[inline]
fn unlock(&self, cycle_offset: usize) {
let idx = cycle_offset / 32;
let mask = 1 << (cycle_offset % 32);
let locked_offsets = unsafe { self.locked_offsets.get_unchecked(idx) };
let prev = locked_offsets.fetch_xor(mask, Ordering::Release);
debug_assert!(prev & mask != 0);
}
#[inline]
fn calc_range(&self, cycle_offset_slices: usize, cycle: usize) -> Range<usize> {
if let Some(cycle_elemidx) = self.cycle_num_elems.checked_mul(cycle) {
if let Some(start) = cycle_elemidx.checked_add(cycle_offset_slices) {
if let Some(end) = start.checked_add(self.slice_len) {
return Range { start, end };
}
}
}
panic!("RepVecRangeLock cycle index out of range.");
}
#[inline]
unsafe fn get_slice(&self, cycle_offset_slices: usize, cycle: usize) -> &[T] {
let data = (*self.data.get()).ptr();
let range = self.calc_range(cycle_offset_slices, cycle);
assert!(range.start <= isize::MAX as usize / size_of::<T>());
unsafe { slice::from_raw_parts(data.add(range.start) as _, range.end - range.start) }
}
#[inline]
#[allow(clippy::mut_from_ref)]
unsafe fn get_mut_slice(&self, cycle_offset_slices: usize, cycle: usize) -> &mut [T] {
let data = (*self.data.get()).ptr();
let range = self.calc_range(cycle_offset_slices, cycle);
assert!(range.start <= isize::MAX as usize / size_of::<T>());
unsafe { slice::from_raw_parts_mut(data.add(range.start) as _, range.end - range.start) }
}
}
#[derive(Debug)]
pub struct RepVecRangeLockGuard<'a, T> {
lock: &'a RepVecRangeLock<T>,
cycle_offset: usize,
cycle_offset_slices: usize,
#[allow(clippy::redundant_allocation)]
_p: PhantomData<Rc<&'a mut T>>,
}
impl<'a, T> RepVecRangeLockGuard<'a, T> {
#[inline]
fn new(
lock: &'a RepVecRangeLock<T>,
cycle_offset: usize,
cycle_offset_slices: usize,
) -> RepVecRangeLockGuard<'a, T> {
RepVecRangeLockGuard {
lock,
cycle_offset,
cycle_offset_slices,
_p: PhantomData,
}
}
}
impl<T> Drop for RepVecRangeLockGuard<'_, T> {
#[inline]
fn drop(&mut self) {
self.lock.unlock(self.cycle_offset);
}
}
impl<T> Index<usize> for RepVecRangeLockGuard<'_, T> {
type Output = [T];
#[inline]
fn index(&self, cycle: usize) -> &Self::Output {
unsafe { self.lock.get_slice(self.cycle_offset_slices, cycle) }
}
}
impl<T> IndexMut<usize> for RepVecRangeLockGuard<'_, T> {
#[inline]
fn index_mut(&mut self, cycle: usize) -> &mut Self::Output {
unsafe { self.lock.get_mut_slice(self.cycle_offset_slices, cycle) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
#[should_panic(expected = "cycle_len out of range")]
fn test_oob_slice_len() {
let _ = RepVecRangeLock::new(vec![0; 100], 1, 0);
}
#[test]
#[should_panic(expected = "cycle_len out of range")]
fn test_oob_cycle_len1() {
let _ = RepVecRangeLock::new(vec![0; 100], 1, usize::MAX - 30);
}
#[test]
#[should_panic(expected = "slice_len must not be 0")]
fn test_oob_cycle_len0() {
let _ = RepVecRangeLock::new(vec![0; 100], 0, 1);
}
#[test]
#[should_panic(expected = "cycle overflow")]
fn test_oob_cycle_len2() {
let _ = RepVecRangeLock::new(vec![0; 100], usize::MAX, 2);
}
#[test]
#[should_panic(expected = "must be 0 <= cycle_offset < cycle_len")]
fn test_oob_lock_offset() {
let a = RepVecRangeLock::new(vec![0; 100], 2, 10);
let _ = a.try_lock(10);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_base_oob_read() {
let a = RepVecRangeLock::new(vec![0; 100], 1, 2);
let g = a.try_lock(0).unwrap();
let _ = g[0][1];
}
#[test]
#[should_panic(expected = "guard 1 panicked")]
fn test_overlap0() {
let a = RepVecRangeLock::new(vec![1_i32, 2, 3, 4, 5, 6], 1, 3);
let _g0 = a.try_lock(0).expect("guard 0 panicked");
let _g1 = a.try_lock(0).expect("guard 1 panicked");
}
#[test]
#[should_panic(expected = "guard 1 panicked")]
fn test_overlap1() {
let a = RepVecRangeLock::new(vec![1_i32, 2, 3, 4, 5, 6], 1, 3);
let _g0 = a.try_lock(1).expect("guard 0 panicked");
let _g1 = a.try_lock(1).expect("guard 1 panicked");
}
#[test]
fn test_big_cycle() {
let a = Arc::new(RepVecRangeLock::new(
vec![1_i32; 256],
2, 128, ));
assert!(a.locked_offsets.len() == 4);
{
let _g = a.try_lock(0);
assert!(a.locked_offsets[0].load(Ordering::Acquire) == 1);
assert!(a.locked_offsets[1].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[2].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[3].load(Ordering::Acquire) == 0);
}
{
let _g = a.try_lock(1);
assert!(a.locked_offsets[0].load(Ordering::Acquire) == 2);
assert!(a.locked_offsets[1].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[2].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[3].load(Ordering::Acquire) == 0);
}
{
let _g = a.try_lock(32);
assert!(a.locked_offsets[0].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[1].load(Ordering::Acquire) == 1);
assert!(a.locked_offsets[2].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[3].load(Ordering::Acquire) == 0);
}
{
let _g = a.try_lock(33);
assert!(a.locked_offsets[0].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[1].load(Ordering::Acquire) == 2);
assert!(a.locked_offsets[2].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[3].load(Ordering::Acquire) == 0);
}
{
let _g = a.try_lock(69);
assert!(a.locked_offsets[0].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[1].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[2].load(Ordering::Acquire) == 32);
assert!(a.locked_offsets[3].load(Ordering::Acquire) == 0);
}
{
let _g = a.try_lock(127);
assert!(a.locked_offsets[0].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[1].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[2].load(Ordering::Acquire) == 0);
assert!(a.locked_offsets[3].load(Ordering::Acquire) == 0x80000000);
}
}
#[test]
#[should_panic(expected = "Invalid cycle_offset")]
fn test_cycle_offset_out_of_range() {
let a = Arc::new(RepVecRangeLock::new(
vec![1_i32; 256],
2, 128, ));
let _g = a.try_lock(128);
}
#[test]
fn test_thread_no_overlap() {
let a = Arc::new(RepVecRangeLock::new(
vec![1_i32, 2, 3, 4],
1, 2, ));
let b = Arc::clone(&a);
let c = Arc::clone(&a);
let ba0 = Arc::new(Barrier::new(2));
let ba1 = Arc::clone(&ba0);
let j0 = thread::spawn(move || {
{
let mut g = b.try_lock(0).unwrap();
assert!(b.locked_offsets[0].load(Ordering::Acquire) & 1 != 0);
assert_eq!(g[0][0], 1);
assert_eq!(g[1][0], 3);
g[0][0] = 10;
g[1][0] = 30;
}
ba0.wait();
});
let j1 = thread::spawn(move || {
{
let g = c.try_lock(1).unwrap();
assert!(c.locked_offsets[0].load(Ordering::Acquire) & 2 != 0);
assert_eq!(g[0][0], 2);
assert_eq!(g[1][0], 4);
}
ba1.wait();
let g = c.try_lock(0).unwrap();
assert_eq!(g[0][0], 10);
assert_eq!(g[1][0], 30);
});
j1.join().expect("Thread 1 panicked.");
j0.join().expect("Thread 0 panicked.");
assert!(a
.locked_offsets
.iter()
.all(|x| x.load(Ordering::Acquire) == 0));
}
#[allow(dead_code)]
struct NoSyncStruct(RefCell<u32>);
#[test]
fn test_nosync() {
let a = Arc::new(RepVecRangeLock::new(
vec![
NoSyncStruct(RefCell::new(1)),
NoSyncStruct(RefCell::new(2)),
NoSyncStruct(RefCell::new(3)),
NoSyncStruct(RefCell::new(4)),
],
1, 2, ));
let b = Arc::clone(&a);
let c = Arc::clone(&a);
let ba0 = Arc::new(Barrier::new(2));
let ba1 = Arc::clone(&ba0);
let j0 = thread::spawn(move || {
let _g = b.try_lock(0).unwrap();
assert!(b.locked_offsets[0].load(Ordering::Acquire) & 1 != 0);
ba0.wait();
});
let j1 = thread::spawn(move || {
let _g = c.try_lock(1).unwrap();
assert!(c.locked_offsets[0].load(Ordering::Acquire) & 2 != 0);
ba1.wait();
});
j1.join().expect("Thread 1 panicked.");
j0.join().expect("Thread 0 panicked.");
assert!(a
.locked_offsets
.iter()
.all(|x| x.load(Ordering::Acquire) == 0));
}
}