use crate::error::Refused;
use crate::park::Parker;
use crate::policy::WaitPolicy;
use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, AtomicUsize, Ordering};
use std::any::Any;
use std::time::Duration;
macro_rules! trace {
($aw:expr, $id:expr, $kind:ident) => {
#[cfg(feature = "trace")]
$aw.trace.record($id, crate::trace::Kind::$kind);
};
}
const ABSENT: usize = 0;
const IDLE: usize = 1;
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
const YIELD: usize = 2;
const RESERVED: usize = 3;
#[repr(align(128))]
pub(crate) struct Padded<T>(pub(crate) T);
#[inline]
pub(crate) fn backoff(spins: &mut u32) {
if *spins < (1 << 14) {
*spins += 1;
std::hint::spin_loop();
} else {
std::thread::yield_now();
}
}
#[repr(align(128))]
struct Slot {
state: AtomicUsize,
gen: AtomicUsize,
ptr: AtomicPtr<Section>,
arg: AtomicUsize,
idle_spin_ns: AtomicU64,
idle_yield: AtomicBool,
park_ns: AtomicU64,
lent: AtomicBool,
park: Parker,
}
impl Slot {
fn new() -> Self {
Slot {
state: AtomicUsize::new(ABSENT),
gen: AtomicUsize::new(0),
ptr: AtomicPtr::new(core::ptr::null_mut()),
arg: AtomicUsize::new(0),
idle_spin_ns: AtomicU64::new(0),
idle_yield: AtomicBool::new(false),
park_ns: AtomicU64::new(u64::MAX),
lent: AtomicBool::new(false),
park: Parker::new(),
}
}
}
pub(crate) struct Awake {
slots: Box<[Slot]>,
pub(crate) shutdown: AtomicBool,
pub(crate) release_epoch: AtomicUsize,
pub(crate) defaults: WaitPolicy,
pub(crate) costs: crate::cost::DispatchCosts,
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub(crate) acquire_timeout: Duration,
#[cfg(feature = "trace")]
pub(crate) trace: crate::trace::Trace,
}
impl Awake {
pub(crate) fn new(
width: usize,
defaults: WaitPolicy,
acquire_timeout: Duration,
costs: crate::cost::DispatchCosts,
) -> Self {
Awake {
slots: (0..width).map(|_| Slot::new()).collect(),
shutdown: AtomicBool::new(false),
release_epoch: AtomicUsize::new(0),
defaults,
costs,
acquire_timeout,
#[cfg(feature = "trace")]
trace: crate::trace::Trace::new(width),
}
}
pub(crate) fn resident(&self) -> usize {
self.slots
.iter()
.filter(|s| s.state.load(Ordering::Acquire) != ABSENT)
.count()
}
pub(crate) fn release(&self, ids: &[usize], policy: WaitPolicy) {
let ns = u64::try_from(policy.idle_spin().as_nanos()).unwrap_or(u64::MAX);
for &id in ids {
let s = &self.slots[id];
s.idle_spin_ns.store(ns, Ordering::Relaxed);
s.idle_yield
.store(policy.idle_runs_rayon(), Ordering::Relaxed);
s.state.store(IDLE, Ordering::SeqCst);
s.park.wake();
}
}
#[cfg_attr(not(feature = "test-hooks"), allow(dead_code))]
pub(crate) fn wake_parked(&self) {
for s in &*self.slots {
s.park.wake();
}
}
pub(crate) fn publish(&self, section: &Section) {
let ptr = (section as *const Section).cast_mut();
for pos in 0..section.dispatched {
let s = &self.slots[section.slot(pos)];
s.arg.store(pos + 1, Ordering::Relaxed);
s.ptr.store(ptr, Ordering::Relaxed);
s.gen.fetch_add(1, Ordering::SeqCst);
}
for pos in 0..section.dispatched {
self.slots[section.slot(pos)].park.wake();
}
trace!(self, None, Publish);
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub(crate) fn lend(&self, ids: &[usize], on: bool) {
for &id in ids {
let s = &self.slots[id];
s.lent.store(on, Ordering::SeqCst);
if on {
s.park.wake();
}
}
}
pub(crate) fn acquire(
self: &std::sync::Arc<Self>,
pool: &crate::pool::Pool,
need: usize,
policy: WaitPolicy,
park_spin: Duration,
) -> Result<Reservation, Refused> {
let mut ids = Vec::with_capacity(need);
let started = std::time::Instant::now();
loop {
let mut yielding = false;
for (i, s) in self.slots.iter().enumerate() {
if ids.len() == need {
break;
}
match s
.state
.compare_exchange(IDLE, RESERVED, Ordering::AcqRel, Ordering::Relaxed)
{
Ok(_) => ids.push(i),
Err(YIELD) => yielding = true,
Err(_) => {}
}
}
if ids.len() == need || !yielding || started.elapsed() >= self.acquire_timeout {
break;
}
std::hint::spin_loop();
}
let missing = need - ids.len();
if missing > 0 {
self.claim_cold(pool, missing, &mut ids);
if ids.len() < need {
self.release(&ids, policy);
return Err(Refused::Unavailable);
}
}
let ns = u64::try_from(park_spin.as_nanos()).unwrap_or(u64::MAX);
for &id in &ids {
self.slots[id].park_ns.store(ns, Ordering::Relaxed);
}
Ok(Reservation {
awake: std::sync::Arc::clone(self),
ids,
policy,
})
}
#[cfg(feature = "parallel")]
fn claim_cold(
self: &std::sync::Arc<Self>,
pool: &crate::pool::Pool,
missing: usize,
ids: &mut Vec<usize>,
) {
use std::sync::Arc;
use std::time::Instant;
let acq = Arc::new(Acq::new(missing));
for _ in 0..missing {
let (aw, a) = (Arc::clone(self), Arc::clone(&acq));
pool.inner.spawn(move || claim_job(&aw, &a));
}
let deadline = Instant::now() + self.acquire_timeout;
let mut spins = 0;
while acq.joined() < missing && Instant::now() < deadline {
backoff(&mut spins);
}
let n = acq.close();
for pos in 0..n {
let mut spins = 0;
loop {
let id = acq.ids[pos].load(Ordering::Acquire);
if id != usize::MAX {
ids.push(id);
break;
}
backoff(&mut spins);
}
}
}
#[cfg(not(feature = "parallel"))]
fn claim_cold(
self: &std::sync::Arc<Self>,
_pool: &crate::pool::Pool,
_missing: usize,
_ids: &mut Vec<usize>,
) {
}
}
pub(crate) struct Reservation {
awake: std::sync::Arc<Awake>,
pub(crate) ids: Vec<usize>,
policy: WaitPolicy,
}
impl Drop for Reservation {
fn drop(&mut self) {
self.awake.release(&self.ids, self.policy);
}
}
#[cfg(feature = "parallel")]
struct Acq {
joined: AtomicUsize,
ids: Vec<AtomicUsize>,
}
#[cfg(feature = "parallel")]
impl Acq {
const CLOSED: usize = 1 << (usize::BITS - 1);
fn new(capacity: usize) -> Self {
Acq {
joined: AtomicUsize::new(0),
ids: (0..capacity)
.map(|_| AtomicUsize::new(usize::MAX))
.collect(),
}
}
fn joined(&self) -> usize {
self.joined.load(Ordering::Acquire) & !Self::CLOSED
}
fn join(&self) -> Option<usize> {
let mut cur = self.joined.load(Ordering::Acquire);
loop {
if cur & Self::CLOSED != 0 || cur >= self.ids.len() {
return None;
}
match self.joined.compare_exchange_weak(
cur,
cur + 1,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return Some(cur),
Err(now) => cur = now,
}
}
}
fn close(&self) -> usize {
self.joined.fetch_or(Self::CLOSED, Ordering::AcqRel) & !Self::CLOSED
}
}
#[cfg(feature = "parallel")]
fn claim_job(aw: &std::sync::Arc<Awake>, acq: &std::sync::Arc<Acq>) {
let Some(w) = rayon::current_thread_index() else {
return;
};
let Some(slot) = aw.slots.get(w) else {
return;
};
if slot
.state
.compare_exchange(ABSENT, RESERVED, Ordering::AcqRel, Ordering::Relaxed)
.is_err()
{
if acq.joined.load(Ordering::Acquire) & Acq::CLOSED == 0 {
let (aw, acq) = (std::sync::Arc::clone(aw), std::sync::Arc::clone(acq));
rayon::spawn(move || claim_job(&aw, &acq));
}
return;
}
let seen = slot.gen.load(Ordering::Acquire);
let epoch = aw.release_epoch.load(Ordering::Relaxed);
match acq.join() {
Some(pos) => {
acq.ids[pos].store(w, Ordering::Release);
member_loop(aw, w, seen, epoch);
}
None => slot.state.store(ABSENT, Ordering::Release),
}
}
#[cfg(feature = "parallel")]
fn member_loop(aw: &Awake, id: usize, mut seen: usize, epoch: usize) {
use std::time::Instant;
let slot = &aw.slots[id];
let mut idle_since: Option<Instant> = None;
let mut last_work: Option<Instant> = None;
let mut was_lent = false;
let mut spins = 0u32;
let mut polls = 0u32;
trace!(aw, Some(id), Enter);
loop {
match slot.state.load(Ordering::Acquire) {
RESERVED if slot.lent.load(Ordering::Acquire) => {
if !was_lent {
was_lent = true;
trace!(aw, Some(id), LentBegin);
}
idle_since = None;
last_work = None;
if !matches!(rayon::yield_now(), Some(rayon::Yield::Executed)) {
for _ in 0..32 {
std::hint::spin_loop();
}
}
}
RESERVED => {
if was_lent {
was_lent = false;
trace!(aw, Some(id), LentEnd);
}
idle_since = None;
let g = slot.gen.load(Ordering::Acquire);
if g == seen {
let since = *last_work.get_or_insert_with(Instant::now);
backoff(&mut spins);
polls = polls.wrapping_add(1);
if polls & 0x3F == 0 {
let park_ns = slot.park_ns.load(Ordering::Relaxed);
if since.elapsed().as_nanos() >= u128::from(park_ns) {
trace!(aw, Some(id), ParkEnter);
park_member(slot, seen);
trace!(aw, Some(id), ParkExit);
last_work = None;
spins = 0;
}
}
} else {
seen = g;
spins = 0;
last_work = None;
let ptr = slot.ptr.load(Ordering::Relaxed);
let arg = slot.arg.load(Ordering::Relaxed);
trace!(aw, Some(id), SectionBegin);
unsafe { Section::execute_member(ptr, arg) };
trace!(aw, Some(id), SectionEnd);
}
}
IDLE => {
let since = *idle_since.get_or_insert_with(Instant::now);
let spin_ns = slot.idle_spin_ns.load(Ordering::Relaxed);
let leave = aw.shutdown.load(Ordering::Relaxed)
|| aw.release_epoch.load(Ordering::Relaxed) != epoch
|| since.elapsed().as_nanos() >= u128::from(spin_ns);
if leave {
if slot
.state
.compare_exchange(IDLE, ABSENT, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
trace!(aw, Some(id), Leave);
return;
}
continue; }
if slot.idle_yield.load(Ordering::Relaxed)
&& slot
.state
.compare_exchange(IDLE, YIELD, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
let ran = rayon::yield_now();
slot.state.store(IDLE, Ordering::Release);
if !matches!(ran, Some(rayon::Yield::Executed)) {
for _ in 0..64 {
std::hint::spin_loop();
}
}
} else {
backoff(&mut spins);
}
}
_ => backoff(&mut spins),
}
}
}
#[cfg(feature = "parallel")]
fn park_member(slot: &Slot, seen: usize) {
slot.park.park_while(|| {
slot.gen.load(Ordering::SeqCst) == seen
&& slot.state.load(Ordering::SeqCst) == RESERVED
&& !slot.lent.load(Ordering::SeqCst)
});
}
pub(crate) struct Section {
call: unsafe fn(*const (), &Section, usize),
data: *const (),
size: usize,
dispatched: usize,
slot_of: unsafe fn(*const (), usize) -> usize,
slot_data: *const (),
remaining: Padded<AtomicUsize>,
arrived: Padded<AtomicUsize>,
epoch: Padded<AtomicUsize>,
cancelled: AtomicBool,
panic: AtomicPtr<Box<dyn Any + Send>>,
}
unsafe impl Sync for Section {}
impl Section {
pub(crate) fn new(
call: unsafe fn(*const (), &Section, usize),
data: *const (),
size: usize,
dispatched: usize,
slot_of: unsafe fn(*const (), usize) -> usize,
slot_data: *const (),
) -> Self {
Section {
call,
data,
size,
dispatched,
slot_of,
slot_data,
remaining: Padded(AtomicUsize::new(dispatched)),
arrived: Padded(AtomicUsize::new(0)),
epoch: Padded(AtomicUsize::new(0)),
cancelled: AtomicBool::new(false),
panic: AtomicPtr::new(core::ptr::null_mut()),
}
}
fn slot(&self, pos: usize) -> usize {
unsafe { (self.slot_of)(self.slot_data, pos) }
}
pub(crate) unsafe fn call_here(&self, arg: usize) {
(self.call)(self.data, self, arg);
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
unsafe fn execute_member(ptr: *const Section, arg: usize) {
let sec = &*ptr;
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sec.call_here(arg)));
if let Err(p) = r {
sec.fail(p);
}
(*ptr).remaining.0.fetch_sub(1, Ordering::Release);
}
pub(crate) fn fail(&self, payload: Box<dyn Any + Send>) {
self.cancelled.store(true, Ordering::Release);
let fresh = Box::into_raw(Box::new(payload));
if self
.panic
.compare_exchange(
core::ptr::null_mut(),
fresh,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
drop(unsafe { Box::from_raw(fresh) });
}
}
pub(crate) fn take_panic(&self) -> Option<Box<dyn Any + Send>> {
let p = self.panic.swap(core::ptr::null_mut(), Ordering::AcqRel);
(!p.is_null()).then(|| *unsafe { Box::from_raw(p) })
}
pub(crate) fn size(&self) -> usize {
self.size
}
pub(crate) fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
pub(crate) fn join(&self) {
let mut spins = 0;
while self.remaining.0.load(Ordering::Acquire) != 0 {
backoff(&mut spins);
}
}
pub(crate) fn barrier(&self) -> bool {
if self.is_cancelled() {
return false;
}
if self.size == 1 {
return true;
}
let epoch = self.epoch.0.load(Ordering::Acquire);
if self.arrived.0.fetch_add(1, Ordering::AcqRel) + 1 == self.size {
self.arrived.0.store(0, Ordering::Relaxed);
self.epoch.0.store(epoch.wrapping_add(1), Ordering::Release);
return true;
}
let mut spins = 0;
loop {
if self.epoch.0.load(Ordering::Acquire) != epoch {
return true;
}
if self.is_cancelled() {
return false;
}
backoff(&mut spins);
}
}
}
pub(crate) struct JoinGuard<'a>(pub(crate) &'a Section);
impl Drop for JoinGuard<'_> {
fn drop(&mut self) {
self.0.join();
}
}
impl Drop for Section {
fn drop(&mut self) {
drop(self.take_panic());
}
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub(crate) struct LendGuard<'a> {
awake: &'a Awake,
ids: &'a [usize],
}
impl<'a> LendGuard<'a> {
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub(crate) fn new(awake: &'a Awake, ids: &'a [usize]) -> Self {
awake.lend(ids, true);
LendGuard { awake, ids }
}
}
impl Drop for LendGuard<'_> {
fn drop(&mut self) {
self.awake.lend(self.ids, false);
}
}