use std::{
cell::UnsafeCell,
ops::Deref,
ptr::NonNull,
sync::atomic::{self, AtomicUsize},
};
use branches::likely;
use index_type::{IndexType, slice::TypedSlice, vec::TypedVec};
use crate::{
atomic_type::Atomic,
per_thread_storage::{ThreadStorageSlotId, ThreadStorageSlotValue},
thread_state::{EncodedThreadState, ThreadState},
};
struct ThreadStorageSlotsCurData {
ptr: *mut ThreadStorageSlotValue,
len: AtomicUsize,
capacity: usize,
}
impl ThreadStorageSlotsCurData {
const fn new() -> Self {
Self {
ptr: NonNull::dangling().as_ptr(),
len: AtomicUsize::new(0),
capacity: 0,
}
}
}
struct WriteLockMarker;
struct CurDataLockMarker;
pub struct ThreadStorageSlots {
cur_data: UnsafeCell<ThreadStorageSlotsCurData>,
cur_data_lock: parking_lot::RwLock<CurDataLockMarker>,
write_lock: std::sync::Mutex<WriteLockMarker>,
free_slots: UnsafeCell<Vec<ThreadStorageSlotId>>,
}
impl ThreadStorageSlots {
pub const fn new() -> Self {
Self {
cur_data: UnsafeCell::new(ThreadStorageSlotsCurData::new()),
cur_data_lock: parking_lot::RwLock::new(CurDataLockMarker),
write_lock: std::sync::Mutex::new(WriteLockMarker),
free_slots: UnsafeCell::new(Vec::new()),
}
}
pub fn read(&self) -> ThreadStorageSlotsReadGuard<'_> {
ThreadStorageSlotsReadGuard {
_guard: self.cur_data_lock.read(),
origin: self,
}
}
unsafe fn cur_data_as_slice(&self) -> &TypedSlice<ThreadStorageSlotId, ThreadStorageSlotValue> {
let cur_data = unsafe { &*self.cur_data.get() };
unsafe {
TypedSlice::from_raw_parts(
cur_data.ptr,
ThreadStorageSlotId::from_raw_index(cur_data.len.load(
atomic::Ordering::Acquire,
)),
)
}
}
fn modify_cur_data<F, R>(
&self,
f: F,
_write_guard: &std::sync::MutexGuard<'_, WriteLockMarker>,
) -> R
where
F: FnOnce(&mut ThreadStorageSlotsCurData) -> R,
{
let _write_guard = self.cur_data_lock.write();
let cur_data = unsafe { &mut *self.cur_data.get() };
f(cur_data)
}
pub fn alloc(&self, initial_thread_state: ThreadState) -> ThreadStorageSlotId {
let encoded_initial_thread_state = initial_thread_state.encode();
let write_guard = self.write_lock.lock().unwrap();
let free_slots = unsafe { &mut *self.free_slots.get() };
match free_slots.pop() {
Some(free_slot_id) => {
unsafe {
self.alloc_from_free_slot(
encoded_initial_thread_state,
free_slot_id,
&write_guard,
)
}
}
None => self.alloc_no_free_slots(encoded_initial_thread_state, write_guard),
}
}
unsafe fn alloc_from_free_slot(
&self,
encoded_initial_thread_state: EncodedThreadState,
free_slot_id: ThreadStorageSlotId,
_write_guard: &std::sync::MutexGuard<'_, WriteLockMarker>,
) -> ThreadStorageSlotId {
let cur_data = unsafe { self.cur_data_as_slice() };
cur_data[free_slot_id].state.store(
encoded_initial_thread_state,
atomic::Ordering::Release,
);
free_slot_id
}
unsafe fn alloc_no_cur_data(
&self,
new_slot_value: ThreadStorageSlotValue,
write_guard: std::sync::MutexGuard<'_, WriteLockMarker>,
) -> ThreadStorageSlotId {
let mut new_data: TypedVec<ThreadStorageSlotId, ThreadStorageSlotValue> =
TypedVec::with_capacity(num_cpus::get() + 1);
new_data.push(new_slot_value);
let (new_data_ptr, new_data_len, new_data_capacity) = new_data.into_raw_parts();
self.modify_cur_data(
|cur_data| {
cur_data.ptr = new_data_ptr;
cur_data.len = AtomicUsize::new(new_data_len);
cur_data.capacity = new_data_capacity;
},
&write_guard,
);
ThreadStorageSlotId::ZERO
}
fn alloc_no_free_slots(
&self,
encoded_initial_thread_state: EncodedThreadState,
write_guard: std::sync::MutexGuard<'_, WriteLockMarker>,
) -> ThreadStorageSlotId {
let new_slot_value = ThreadStorageSlotValue {
state: Atomic::<EncodedThreadState>::new(encoded_initial_thread_state),
};
let cur_data = unsafe { &*self.cur_data.get() };
if cur_data.capacity == 0 {
unsafe { self.alloc_no_cur_data(new_slot_value, write_guard) }
} else {
unsafe { self.alloc_no_free_slots_grow_cur_data(new_slot_value, write_guard) }
}
}
unsafe fn alloc_no_free_slots_grow_cur_data(
&self,
new_slot_value: ThreadStorageSlotValue,
write_guard: std::sync::MutexGuard<'_, WriteLockMarker>,
) -> ThreadStorageSlotId {
let cur_data = unsafe { &*self.cur_data.get() };
let len = cur_data.len.load(
atomic::Ordering::Relaxed,
);
let mut new_data: TypedVec<ThreadStorageSlotId, ThreadStorageSlotValue> =
unsafe { TypedVec::from_raw_parts_unchecked(cur_data.ptr, len, cur_data.capacity) };
if likely(len < cur_data.capacity) {
let new_slot_id = new_data
.try_push(new_slot_value)
.expect("too many concurrent threads");
cur_data.len.store(
new_data.len().to_raw_index(),
atomic::Ordering::Release,
);
core::mem::forget(new_data);
new_slot_id
} else {
self.modify_cur_data(
|cur_data| {
let new_slot_id = new_data.push(new_slot_value);
let (new_data_ptr, new_data_len, new_data_capacity) = new_data.into_raw_parts();
cur_data.ptr = new_data_ptr;
cur_data.len = AtomicUsize::new(new_data_len);
cur_data.capacity = new_data_capacity;
new_slot_id
},
&write_guard,
)
}
}
pub unsafe fn dealloc(&self, slot_id: ThreadStorageSlotId) {
let _write_guard = self.write_lock.lock().unwrap();
let cur_data = unsafe { self.cur_data_as_slice() };
cur_data[slot_id].state.store(
ThreadState::NONE_ENCODED_VALUE,
atomic::Ordering::Release,
);
let free_slots = unsafe { &mut *self.free_slots.get() };
free_slots.push(slot_id);
}
}
impl Drop for ThreadStorageSlots {
fn drop(&mut self) {
let cur_data = self.cur_data.get_mut();
if cur_data.capacity != 0 {
let _ = unsafe {
TypedVec::<ThreadStorageSlotId, ThreadStorageSlotValue>::from_raw_parts_unchecked(
cur_data.ptr,
cur_data.len.load(atomic::Ordering::Relaxed),
cur_data.capacity,
)
};
}
}
}
unsafe impl Sync for ThreadStorageSlots {}
pub struct ThreadStorageSlotsReadGuard<'a> {
_guard: parking_lot::RwLockReadGuard<'a, CurDataLockMarker>,
origin: &'a ThreadStorageSlots,
}
impl<'a> Deref for ThreadStorageSlotsReadGuard<'a> {
type Target = TypedSlice<ThreadStorageSlotId, ThreadStorageSlotValue>;
fn deref(&self) -> &Self::Target {
unsafe { self.origin.cur_data_as_slice() }
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic;
use crate::{
epoch::{EPOCH_ID_MIN, EpochId},
per_thread_storage::{ThreadStorageSlotId, ThreadStorageSlots},
thread_state::ThreadState,
};
#[test]
fn test_basic() {
let slots = ThreadStorageSlots::new();
let thread_state = ThreadState {
last_seen_epoch_id: EPOCH_ID_MIN,
is_busy: true,
};
let slot_id = slots.alloc(thread_state);
let read_guard = slots.read();
assert_eq!(
read_guard[slot_id].state.load(atomic::Ordering::Relaxed),
thread_state.encode()
);
unsafe { slots.dealloc(slot_id) };
}
#[test]
fn test_multiple_allocs() {
const NUM_ALLOCS: u16 = 1024;
fn thread_state_by_alloc_index(alloc_index: u16) -> ThreadState {
ThreadState {
last_seen_epoch_id: ((alloc_index + 1) * 2) as EpochId,
is_busy: true,
}
}
let slots = ThreadStorageSlots::new();
let slot_ids: Vec<ThreadStorageSlotId> = (0..NUM_ALLOCS)
.map(|i| slots.alloc(thread_state_by_alloc_index(i)))
.collect();
for i in 0..NUM_ALLOCS {
let slot_id = slot_ids[i as usize];
assert_eq!(
slots.read()[slot_id].state.load(atomic::Ordering::Relaxed),
thread_state_by_alloc_index(i).encode()
);
}
for slot in slot_ids {
unsafe { slots.dealloc(slot) };
}
}
#[test]
fn test_realloc() {
let slots = ThreadStorageSlots::new();
let thread_state = ThreadState {
last_seen_epoch_id: EPOCH_ID_MIN,
is_busy: true,
};
let id = slots.alloc(thread_state);
unsafe { slots.dealloc(id) };
for _ in 0..128 {
let new_id = slots.alloc(thread_state);
assert_eq!(id, new_id);
unsafe { slots.dealloc(new_id) };
}
}
}