use std::{
array::from_fn,
cell::UnsafeCell,
fmt,
iter::repeat_with,
mem::{align_of, offset_of, size_of},
sync::{
Arc,
atomic::{AtomicI64, AtomicU32, AtomicU64, Ordering, fence},
},
thread::yield_now,
};
use log::{debug, trace};
use wbase::{backoff::Backoff, thread::current_thread_id};
use whasher::mix_thread_id;
use crate::{
EpochEntry, Error, MAX_USER_WORDS, Participant, ProtectedScope, Result,
tls::{
FAST_ENTRY, FAST_PARTICIPANT, cached_slot, clear_thread_entry, get_thread_entry,
note_participant_slot, set_thread_entry,
},
};
pub const DRAIN_LIST_SIZE: usize = 16;
const DRAIN_ENTRY_FREE: u64 = u64::MAX;
const DRAIN_ENTRY_CLAIMING: u64 = u64::MAX - 1;
#[repr(align(64))]
struct DrainEntry {
epoch: AtomicU64,
action: UnsafeCell<Option<Box<dyn FnOnce() + Send + 'static>>>,
}
unsafe impl Send for DrainEntry {}
unsafe impl Sync for DrainEntry {}
const _: () = assert!(size_of::<DrainEntry>() == 64);
const _: () = assert!(align_of::<DrainEntry>() == 64);
impl DrainEntry {
const fn new() -> Self {
Self {
epoch: AtomicU64::new(DRAIN_ENTRY_FREE),
action: UnsafeCell::new(None),
}
}
}
static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
#[repr(C, align(64))]
pub struct LightEpoch {
pub id: u64,
pub current_epoch: AtomicU64,
_pad0: [u8; 48],
pub safe_to_reclaim_epoch: AtomicU64,
_pad1: [u8; 56],
pub drain_count: AtomicU32,
pub user_word_mask: AtomicU32,
_pad2: [u8; 56],
pub entries: Arc<[EpochEntry]>,
drain_list: Box<[DrainEntry; DRAIN_LIST_SIZE]>,
}
impl LightEpoch {
pub const DEFAULT_MAX_THREADS: usize = 128;
pub fn new(max_threads: usize) -> Self {
let max_threads = max_threads.max(1);
let entries: Arc<[EpochEntry]> = repeat_with(EpochEntry::new).take(max_threads).collect();
Self {
id: NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed),
current_epoch: AtomicU64::new(1),
_pad0: [0; 48],
safe_to_reclaim_epoch: AtomicU64::new(0),
_pad1: [0; 56],
drain_count: AtomicU32::new(0),
user_word_mask: AtomicU32::new(0),
_pad2: [0; 56],
entries,
drain_list: Box::new(from_fn(|_| DrainEntry::new())),
}
}
pub fn register(self: &Arc<Self>) -> Result<Participant> {
for (idx, entry) in self.entries.iter().enumerate() {
if entry.try_reserve() {
trace!("成功注册参与者,分配条目索引: {idx}");
note_participant_slot(self.id, idx, entry);
return Ok(Participant::new(Arc::clone(self), idx));
}
}
Err(Error::ExceededMaxThreads(self.entries.len()))
}
#[inline]
fn active_idx(&self) -> Option<usize> {
let entry = get_thread_entry(self.id);
(entry != 0 && entry <= self.entries.len()).then(|| entry - 1)
}
#[inline]
fn tls_protected_entry(&self, tid: u64) -> Option<&EpochEntry> {
let fe = FAST_ENTRY.get();
if fe.instance_id == self.id && fe.slot != 0 {
let entry = unsafe { &*fe.ptr };
if entry.thread_id() == tid && entry.is_protected() {
return Some(entry);
}
}
let idx = self.active_idx()?;
let entry = unsafe {
self.entries.get_unchecked(idx)
};
(entry.thread_id() == tid && entry.is_protected()).then_some(entry)
}
#[inline]
pub(crate) fn drain_if_pending(&self) {
if self.drain_count.load(Ordering::Acquire) > 0 {
self.drain();
}
}
#[inline]
fn help_drain_if_pending(&self) {
if self.drain_count.load(Ordering::Acquire) > 0 {
self.help_drain();
}
}
#[inline]
fn claim_entry(&self, idx: usize, tid: u64) -> bool {
let entry = unsafe {
self.entries.get_unchecked(idx)
};
if !entry.try_claim(tid, &self.current_epoch) {
return false;
}
set_thread_entry(self.id, idx + 1, || Arc::downgrade(&self.entries));
self.help_drain_if_pending();
true
}
pub fn resume(&self) {
let tid = current_thread_id();
if let Some(entry) = self.tls_protected_entry(tid) {
entry.inc_reentrant();
self.drain_if_pending();
return;
}
let len = self.entries.len();
if let Some(idx) = cached_slot(self.id).checked_sub(1).filter(|&idx| idx < len)
&& self.claim_entry(idx, tid)
{
return;
}
let start = mix_thread_id(tid) % len;
let mut backoff = Backoff::new();
loop {
for offset in 0..len {
let sum = start + offset;
if self.claim_entry(if sum < len { sum } else { sum - len }, tid) {
return;
}
}
backoff.snooze();
}
}
pub fn suspend(&self) {
let Some(entry) = self.tls_protected_entry(current_thread_id()) else {
return;
};
if !entry.exit() {
return;
}
clear_thread_entry(self.id);
self.after_release();
}
#[inline]
pub(crate) fn after_release(&self) {
if self.drain_count.load(Ordering::Acquire) > 0 {
self.suspend_drain();
}
}
pub fn try_suspend(&self) -> bool {
if self.this_instance_protected() {
self.suspend();
true
} else {
false
}
}
pub fn resume_if_not_protected(&self) -> bool {
if self.this_instance_protected() {
false
} else {
self.resume();
true
}
}
pub fn this_instance_protected(&self) -> bool {
self.tls_protected_entry(current_thread_id()).is_some()
}
pub fn thread_protected(&self) -> bool {
self.thread_protected_entry().is_some()
}
fn thread_protected_entry(&self) -> Option<&EpochEntry> {
let tid = current_thread_id();
if let Some(entry) = self.tls_protected_entry(tid) {
return Some(entry);
}
let fp = FAST_PARTICIPANT.get();
if fp.instance_id == self.id && fp.slot != 0 {
let entry = unsafe { &*fp.ptr };
if entry.thread_id() == tid && entry.is_protected() {
return Some(entry);
}
}
self
.entries
.iter()
.find(|entry| entry.is_protected() && entry.thread_id() == tid)
}
pub fn suspend_resume(&self) {
self.suspend();
self.resume();
}
pub fn protect_and_drain(&self) {
let Some(idx) = self.active_idx() else {
debug_assert!(false, "试图刷新未受保护的纪元");
return;
};
let entry = unsafe { self.entries.get_unchecked(idx) };
debug_assert!(
entry.thread_id() == current_thread_id() && entry.is_protected(),
"试图刷新未受保护的纪元"
);
let current = self.current_epoch();
entry.refresh_epoch(current);
self.drain_if_pending();
}
pub fn protected_scope(&self) -> ProtectedScope<'_> {
ProtectedScope::new(self)
}
pub fn bump_current_epoch(&self) -> u64 {
let new_epoch = self.current_epoch.fetch_add(1, Ordering::AcqRel) + 1;
trace!("递增全局纪元至: {new_epoch}");
if self.drain_count.load(Ordering::Acquire) > 0 {
self.drain();
} else {
self.compute_safe_to_reclaim_epoch();
}
new_epoch
}
#[inline]
pub fn bump_epoch(&self) -> u64 {
self.bump_current_epoch()
}
fn help_drain(&self) {
let current = self.current_epoch.load(Ordering::Acquire);
let tid = current_thread_id();
for entry in self.entries.iter() {
if entry.is_protected() && entry.thread_id() == tid {
entry.refresh_epoch(current);
}
}
self.drain();
}
pub fn bump_current_epoch_action<F>(&self, on_drain: F)
where
F: FnOnce() + Send + 'static,
{
let prior_epoch = self.bump_current_epoch() - 1;
let mut action_opt = Some(Box::new(on_drain) as Box<dyn FnOnce() + Send + 'static>);
'outer: loop {
for entry in self.drain_list.iter() {
let curr_epoch = entry.epoch.load(Ordering::Acquire);
if (curr_epoch == DRAIN_ENTRY_FREE
|| curr_epoch <= self.safe_to_reclaim_epoch.load(Ordering::Acquire))
&& entry
.epoch
.compare_exchange(
curr_epoch,
DRAIN_ENTRY_CLAIMING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
let new_action = action_opt.take();
let prev_action = unsafe {
let ptr = entry.action.get();
let prev = (*ptr).take();
*ptr = new_action;
prev
};
if curr_epoch == DRAIN_ENTRY_FREE {
self.drain_count.fetch_add(1, Ordering::AcqRel);
}
entry.epoch.store(prior_epoch, Ordering::Release);
if let Some(act) = prev_action {
act();
}
break 'outer;
}
}
self.help_drain();
yield_now();
}
self.help_drain();
}
#[inline]
pub fn current_epoch(&self) -> u64 {
self.current_epoch.load(Ordering::Acquire)
}
#[inline]
pub fn safe_to_reclaim_epoch(&self) -> u64 {
self.safe_to_reclaim_epoch.load(Ordering::Acquire)
}
pub fn compute_safe_to_reclaim_epoch(&self) -> u64 {
let curr = self.current_epoch.load(Ordering::Acquire);
let mut oldest = curr;
for entry in self.entries.iter() {
let epoch = entry.protected_epoch();
if epoch != 0 && epoch < oldest {
oldest = epoch;
if oldest == 1 {
break;
}
}
}
let safe = oldest.saturating_sub(1);
let prev = self.safe_to_reclaim_epoch.load(Ordering::Relaxed);
if safe > prev {
self.safe_to_reclaim_epoch.fetch_max(safe, Ordering::AcqRel);
}
prev.max(safe)
}
#[inline]
fn try_claim_ready_slot(entry: &DrainEntry, safe_epoch: u64) -> bool {
let trigger_epoch = entry.epoch.load(Ordering::Acquire);
trigger_epoch <= safe_epoch
&& entry
.epoch
.compare_exchange(
trigger_epoch,
DRAIN_ENTRY_CLAIMING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
pub fn drain(&self) {
let safe_epoch = self.compute_safe_to_reclaim_epoch();
for entry in self.drain_list.iter() {
if Self::try_claim_ready_slot(entry, safe_epoch) {
let action = unsafe { (*entry.action.get()).take() };
self.drain_count.fetch_sub(1, Ordering::AcqRel);
entry.epoch.store(DRAIN_ENTRY_FREE, Ordering::Release);
if let Some(act) = action {
act();
}
if self.drain_count.load(Ordering::Acquire) == 0 {
break;
}
}
}
}
fn suspend_drain(&self) {
while self.drain_count.load(Ordering::Acquire) > 0 {
fence(Ordering::SeqCst);
if self.entries.iter().any(EpochEntry::is_protected) {
return;
}
self.drain();
yield_now();
}
}
#[inline]
pub fn is_safe_to_reclaim(&self, target_epoch: u64) -> bool {
if target_epoch <= self.safe_to_reclaim_epoch.load(Ordering::Acquire) {
return true;
}
target_epoch <= self.compute_safe_to_reclaim_epoch()
}
pub fn bump_and_wait(&self, target_epoch: u64) {
debug!("开始 bump_and_wait 等待纪元 {target_epoch} 完全 drain");
debug_assert!(
!self.entries.iter().any(|e| e.is_protected()
&& e.thread_id() == current_thread_id()
&& e.protected_epoch() <= target_epoch),
"bump_and_wait 活锁:调用线程以 ≤ {target_epoch} 的纪元受保护,须先 suspend/refresh"
);
while self.current_epoch.load(Ordering::Acquire) <= target_epoch {
self.bump_epoch();
}
let mut backoff = Backoff::new();
while !self.is_safe_to_reclaim(target_epoch) {
self.drain_if_pending();
backoff.snooze();
}
self.drain_if_pending();
debug!("纪元 {target_epoch} drain 完成");
}
#[inline]
pub fn has_pending_drain(&self) -> bool {
self.drain_count.load(Ordering::Acquire) > 0
}
#[inline]
pub fn test_hook_this_thread_entry(&self) -> usize {
get_thread_entry(self.id)
}
#[inline]
pub fn test_hook_this_thread_announced_epoch(&self) -> u64 {
self
.active_idx()
.map(|idx| unsafe { self.entries.get_unchecked(idx).protected_epoch() })
.unwrap_or(0)
}
#[inline]
pub fn test_hook_announced_epoch_at(&self, entry: usize) -> u64 {
if entry == 0 || entry > self.entries.len() {
0
} else {
unsafe { self.entries.get_unchecked(entry - 1).protected_epoch() }
}
}
#[inline]
pub fn test_hook_thread_id_at(&self, entry: usize) -> u64 {
if entry == 0 || entry > self.entries.len() {
0
} else {
unsafe { self.entries.get_unchecked(entry - 1).thread_id() }
}
}
#[inline]
pub fn test_hook_drain_list_capacity(&self) -> usize {
DRAIN_LIST_SIZE
}
#[inline]
pub fn entry_count(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn test_hook_max_user_words(&self) -> usize {
MAX_USER_WORDS
}
pub fn allocate_user_word(&self, initial_value: i64) -> Result<usize> {
loop {
let mask = self.user_word_mask.load(Ordering::Acquire);
let idx = (!mask).trailing_zeros() as usize;
if idx >= MAX_USER_WORDS {
return Err(Error::ExceededMaxUserWords(MAX_USER_WORDS));
}
let new_mask = mask | (1 << idx);
if self
.user_word_mask
.compare_exchange_weak(mask, new_mask, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
continue;
}
for entry in self.entries.iter() {
unsafe { entry.set_user_word_unchecked(idx, initial_value) };
}
return Ok(idx);
}
}
pub fn release_user_word(&self, word_index: usize) -> Result<()> {
if word_index >= MAX_USER_WORDS {
return Err(Error::InvalidUserWordIndex(word_index));
}
loop {
let mask = self.user_word_mask.load(Ordering::Acquire);
let new_mask = mask & !(1 << word_index);
if self
.user_word_mask
.compare_exchange_weak(mask, new_mask, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return Ok(());
}
}
}
#[inline]
pub fn this_thread_user_word_atomic(&self, word_index: usize) -> Result<&AtomicI64> {
if word_index >= MAX_USER_WORDS {
return Err(Error::InvalidUserWordIndex(word_index));
}
let idx = self.active_idx().ok_or(Error::NotProtected)?;
unsafe {
Ok(
self
.entries
.get_unchecked(idx)
.user_word_atomic_unchecked(word_index),
)
}
}
#[inline]
pub fn this_thread_user_word(&self, word_index: usize) -> Result<i64> {
Ok(
self
.this_thread_user_word_atomic(word_index)?
.load(Ordering::Acquire),
)
}
#[inline]
pub fn set_this_thread_user_word(&self, word_index: usize, val: i64) -> Result<()> {
self
.this_thread_user_word_atomic(word_index)?
.store(val, Ordering::Release);
Ok(())
}
pub fn get_min_user_word(&self, word_index: usize) -> Result<i64> {
if word_index >= MAX_USER_WORDS {
return Err(Error::InvalidUserWordIndex(word_index));
}
Ok(self.entries.iter().fold(i64::MAX, |min, e| {
unsafe { e.user_word_unchecked(word_index) }.min(min)
}))
}
}
impl Default for LightEpoch {
fn default() -> Self {
Self::new(Self::DEFAULT_MAX_THREADS)
}
}
const _: () = {
assert!(size_of::<LightEpoch>().is_multiple_of(64));
assert!(align_of::<LightEpoch>() == 64);
assert!(offset_of!(LightEpoch, current_epoch) == 8);
assert!(offset_of!(LightEpoch, safe_to_reclaim_epoch) == 64);
assert!(offset_of!(LightEpoch, drain_count) == 128);
assert!(offset_of!(LightEpoch, user_word_mask) == 132);
};
impl fmt::Debug for LightEpoch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LightEpoch")
.field("id", &self.id)
.field("current_epoch", &self.current_epoch.load(Ordering::Relaxed))
.field(
"safe_to_reclaim_epoch",
&self.safe_to_reclaim_epoch.load(Ordering::Relaxed),
)
.field("drain_count", &self.drain_count.load(Ordering::Relaxed))
.field("max_threads", &self.entries.len())
.finish()
}
}