use crate::error::Refused;
use crate::park::Parker;
use crate::policy::WaitPolicy;
use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, 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;
#[inline]
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
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,
spin_budget: AtomicU32,
lent: AtomicBool,
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
saved: crate::pin::SavedMask,
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),
spin_budget: AtomicU32::new(crate::park::THROTTLED_POLLS),
lent: AtomicBool::new(false),
saved: crate::pin::SavedMask::new(),
park: Parker::new(),
}
}
}
pub(crate) struct Waits {
pub(crate) sync: Parker,
pub(crate) spin_polls: u32,
pub(crate) join_sleeps: AtomicU64,
pub(crate) barrier_sleeps: AtomicU64,
pub(crate) member_parks: AtomicU64,
}
pub(crate) struct Awake {
slots: Box<[Slot]>,
pub(crate) waits: Waits,
pub(crate) shutdown: AtomicBool,
pub(crate) release_epoch: AtomicUsize,
pub(crate) defaults: WaitPolicy,
pub(crate) costs: crate::cost::DispatchCosts,
pub(crate) pin: bool,
#[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,
pin: bool,
spin_polls: u32,
) -> Self {
Awake {
slots: (0..width).map(|_| Slot::new()).collect(),
waits: Waits {
sync: Parker::new(),
spin_polls,
join_sleeps: AtomicU64::new(0),
barrier_sleeps: AtomicU64::new(0),
member_parks: AtomicU64::new(0),
},
shutdown: AtomicBool::new(false),
release_epoch: AtomicUsize::new(0),
defaults,
costs,
pin,
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();
}
}
pub(crate) fn spin_budget(&self, over: bool) -> u32 {
if over {
crate::park::THROTTLED_POLLS.min(self.waits.spin_polls)
} else {
self.waits.spin_polls
}
}
#[cfg(all(test, feature = "parallel", target_os = "linux"))]
pub(crate) fn slot_spin_budget(&self, id: usize) -> u32 {
self.slots[id].spin_budget.load(Ordering::Relaxed)
}
pub(crate) fn set_spin_budget(&self, ids: &[usize], polls: u32) {
for &id in ids {
self.slots[id].spin_budget.store(polls, Ordering::Relaxed);
}
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub(crate) fn unpin_member(&self, id: usize) {
if let Some(slot) = self.slots.get(id) {
slot.saved.restore();
}
}
#[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,
spin_budget: u32,
) -> 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);
}
}
self.set_spin_budget(&ids, spin_budget);
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 crate::park::{Step, Waiter};
use std::time::Instant;
let slot = &aw.slots[id];
let mut idle_since: Option<Instant> = None;
let mut idle_polls = 0u64;
let mut waiter: Option<Waiter> = None;
let mut was_lent = false;
let mut reserved = true;
let mut spins = 0u32;
trace!(aw, Some(id), Enter);
loop {
let state = slot.state.load(Ordering::Acquire);
if state == RESERVED {
reserved = true;
} else if reserved {
reserved = false;
slot.saved.restore();
}
match state {
RESERVED if slot.lent.load(Ordering::Acquire) => {
if !was_lent {
was_lent = true;
slot.saved.restore();
trace!(aw, Some(id), LentBegin);
}
idle_since = None;
waiter = 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 w = waiter.get_or_insert_with(|| {
Waiter::new(slot.spin_budget.load(Ordering::Relaxed))
});
if w.step() == Step::Sleep {
aw.waits.member_parks.fetch_add(1, Ordering::Relaxed);
trace!(aw, Some(id), ParkEnter);
park_member(slot, seen);
trace!(aw, Some(id), ParkExit);
waiter = None;
}
} else {
seen = g;
waiter = None;
let ptr = slot.ptr.load(Ordering::Relaxed);
let arg = slot.arg.load(Ordering::Relaxed);
if unsafe { (*ptr).pin } {
slot.saved.save_if_unpinned();
}
trace!(aw, Some(id), SectionBegin);
unsafe { Section::execute_member(ptr, arg) };
trace!(aw, Some(id), SectionEnd);
}
}
IDLE => {
waiter = None;
if idle_since.is_none() {
idle_polls = 0;
}
let since = *idle_since.get_or_insert_with(Instant::now);
idle_polls += 1;
let spin_ns = slot.idle_spin_ns.load(Ordering::Relaxed);
let leave = aw.shutdown.load(Ordering::Relaxed)
|| aw.release_epoch.load(Ordering::Relaxed) != epoch
|| idle_polls > u64::from(aw.waits.spin_polls)
|| 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 (),
pub(crate) over: bool,
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pin: bool,
done: crate::park::Completion,
barrier: crate::park::Barrier,
cancelled: crate::sync::AtomicBool,
waits: *const Waits,
panic: AtomicPtr<Box<dyn Any + Send>>,
}
unsafe impl Sync for Section {}
#[derive(Clone, Copy)]
pub(crate) struct Geometry {
pub(crate) dispatched: usize,
pub(crate) size: usize,
pub(crate) over: bool,
pub(crate) pin: bool,
}
impl Section {
pub(crate) fn new(
call: unsafe fn(*const (), &Section, usize),
data: *const (),
slot_of: unsafe fn(*const (), usize) -> usize,
slot_data: *const (),
geometry: Geometry,
waits: Option<&Waits>,
) -> Self {
let Geometry {
dispatched,
size,
over,
pin,
} = geometry;
Section {
call,
data,
size,
dispatched,
slot_of,
slot_data,
over,
pin,
done: crate::park::Completion::new(dispatched),
barrier: crate::park::Barrier::new(size),
cancelled: crate::sync::AtomicBool::new(false),
waits: waits.map_or(core::ptr::null(), |w| w as *const Waits),
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 waits = (*ptr).waits;
{
let sec = &*ptr;
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sec.call_here(arg)));
if let Err(p) = r {
sec.fail(p);
}
}
crate::park::Completion::finish(
core::ptr::addr_of!((*ptr).done),
waits.as_ref().map(|w| &w.sync),
);
}
fn waits(&self) -> Option<&Waits> {
unsafe { self.waits.as_ref() }
}
fn waiter(&self) -> crate::park::Waiter {
match self.waits() {
Some(w) => crate::park::Waiter::new(if self.over {
crate::park::THROTTLED_POLLS.min(w.spin_polls)
} else {
w.spin_polls
}),
None => crate::park::Waiter::new(u32::MAX),
}
}
pub(crate) fn fail(&self, payload: Box<dyn Any + Send>) {
self.cancelled.store(true, Ordering::SeqCst);
if let Some(w) = self.waits() {
w.sync.wake();
}
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) {
if self.done.is_done() {
return;
}
let mut waiter = self.waiter();
self.done.wait(self.waits().map(|w| &w.sync), &mut waiter);
if waiter.slept() {
if let Some(w) = self.waits() {
w.join_sleeps.fetch_add(1, Ordering::Relaxed);
}
}
}
pub(crate) fn barrier(&self) -> bool {
let mut waiter = self.waiter();
let passed =
self.barrier
.arrive(&self.cancelled, self.waits().map(|w| &w.sync), &mut waiter);
if waiter.slept() {
if let Some(w) = self.waits() {
w.barrier_sleeps.fetch_add(1, Ordering::Relaxed);
}
}
passed
}
}
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);
}
}