use crate::{
TraceObj,
epoch::{Color, Epoch, Phase},
internal::{Local, OBJ_BATCH_SIZE},
pointers::ManObj,
sync::Entry,
task::Task,
tls::global,
};
use crossbeam::epoch::pin as ebr_pin;
use std::{
cell::{Cell, LazyCell},
rc::Rc,
sync::atomic::{Ordering, compiler_fence, fence},
};
const HELP_NORMAL_MAX_TRIAL: usize = 2;
const HELP_TRACING_MAX_TRIAL: usize = 4;
#[derive(Clone)]
pub struct Handle {
pub(crate) local: Rc<Entry<Local>>,
}
impl Handle {
#[inline]
pub(crate) fn local(&self) -> &Local {
&self.local
}
#[inline]
pub fn pin(&self) -> Guard {
let guard = Guard::new(self.local.clone());
self.local().pin_inner();
guard
}
#[inline]
pub fn is_pinned(&self) -> bool {
self.local().is_pinned()
}
#[inline]
pub fn help_collect(&self) {
self.pin().help_collect();
}
}
pub struct Guard {
local: Rc<Entry<Local>>,
should_help: Cell<bool>,
}
impl Guard {
fn new(local: Rc<Entry<Local>>) -> Self {
Self {
local,
should_help: Cell::new(false),
}
}
pub fn repin(&mut self) {
self.help_collect_if_scheduled();
self.local().repin();
}
pub(crate) fn local(&self) -> &Local {
&self.local
}
pub(crate) fn local_epoch(&self) -> Epoch {
unsafe { self.local().pinned_epoch() }
}
pub(crate) fn phase(&self) -> Phase {
self.local_epoch().phase()
}
pub(crate) fn white_color(&self) -> Color {
self.local_epoch().color()
}
pub(crate) fn black_color(&self) -> Color {
self.white_color().flip()
}
pub(crate) fn alloc_color(&self) -> Color {
unsafe { self.local().pinned_alloc_color() }
}
#[inline]
pub(crate) fn global_phase_with_fence(&self) -> Phase {
if cfg!(any(target_arch = "x86_64", target_arch = "x86")) {
compiler_fence(Ordering::SeqCst);
} else {
fence(Ordering::SeqCst);
}
global().load_epoch().phase()
}
pub(crate) fn alloc<T: TraceObj>(&self, obj: ManObj<T>) -> *mut ManObj<T> {
self.local().alloc(obj, self)
}
pub(crate) fn schedule_mark<T: TraceObj>(&self, obj: &ManObj<T>) {
self.local().schedule_mark(obj, self);
}
pub(crate) fn try_pop_mark_task(&self) -> Option<Task> {
self.local().try_pop_mark_task()
}
pub(crate) fn schedule_helping_collect(&self) {
self.should_help.set(true);
}
pub(crate) fn help_collect_if_scheduled(&self) {
if self.should_help.get() {
self.should_help.set(false);
self.help_collect();
}
}
pub fn help_collect(&self) {
match self.phase() {
Phase::N => self.help_normal(),
Phase::RT | Phase::CT => self.help_tracing(),
}
}
#[inline]
pub(crate) fn help_normal(&self) {
if self.phase() != Phase::N {
return;
}
call_without_recursion(&self.local().is_helping_normal, || self.help_normal_inner());
}
#[inline]
fn help_normal_inner(&self) {
let ebr_guard = &ebr_pin();
let mut trial_count = 0;
for q_idx in self.local().generate_shard_permut() {
let prev_white = self.black_color();
let marked_q = &global().marked_objs[prev_white as usize][q_idx];
while let Some(batch) = marked_q.try_pop(ebr_guard) {
let mut reclaimed_bytes = 0;
for obj in batch.into_iter() {
if prev_white == obj.color() {
reclaimed_bytes += size_of_val(&*obj);
drop(obj);
continue;
}
unsafe { self.local().push_fresh_obj(obj, false) };
}
global()
.stats
.total_reclaimed
.fetch_add(reclaimed_bytes, Ordering::Release);
trial_count += 1;
if trial_count >= HELP_NORMAL_MAX_TRIAL {
return;
}
}
}
}
#[inline]
pub(crate) fn help_tracing(&self) {
if self.phase() != Phase::RT && self.phase() != Phase::CT {
return;
}
if !self.is_tracing_synced() {
return;
}
call_without_recursion(&self.local().is_helping_root_tracing, || {
self.help_root_tracing()
});
call_without_recursion(&self.local().is_helping_draining_mark_tasks, || {
self.help_draining_mark_tasks_inner()
});
}
#[inline]
fn is_tracing_synced(&self) -> bool {
global().locals.iter_using().all(|local| {
let other_epoch = local.epoch.load(Ordering::Relaxed);
!other_epoch.is_pinned() || other_epoch.phase() != Phase::N
})
}
#[inline]
pub(crate) fn help_draining_mark_tasks_inner(&self) {
let mt_len = unsafe { &*self.local().mark_tasks.get() }.len();
for _ in 0..((mt_len / 2).max(OBJ_BATCH_SIZE / 2)) {
if let Some(task) = self.try_pop_mark_task() {
task.call(self);
continue;
}
break;
}
}
#[inline]
fn help_root_tracing(&self) {
if self.phase() != Phase::RT {
return;
}
let ebr_guard = &ebr_pin();
let hazards = LazyCell::new(|| self.local().scan_or_reuse_hazards(self, ebr_guard));
let mut trial_count = 0;
for q_idx in self.local().generate_shard_permut() {
let fresh_q = &global().fresh_objs[self.white_color() as usize][q_idx];
while let Some(batch) = fresh_q.try_pop(ebr_guard) {
for obj in batch.iter() {
if obj.root_count() > 0 || hazards.contains(&obj.address()) {
obj.mark(self);
}
}
let marked_q_idx = self.local().select_obj_shard();
let marked_q = &global().marked_objs[self.white_color() as usize][marked_q_idx];
marked_q.push(batch, ebr_guard);
trial_count += 1;
if trial_count >= HELP_TRACING_MAX_TRIAL {
return;
}
}
}
}
}
impl Drop for Guard {
#[inline]
fn drop(&mut self) {
self.help_collect_if_scheduled();
self.local().unpin_inner();
}
}
#[inline]
fn call_without_recursion(flag: &Cell<bool>, f: impl FnOnce()) {
if flag.get() {
return;
}
flag.set(true);
f();
flag.set(false);
}