use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use super::super::{idle::IdleBitset, types::SchedulerInner};
use super::next_steal_start;
pub(super) fn should_stop<const BLOCKING_QUEUE_CAPACITY: usize>(
inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
) -> bool {
inner.shutdown.load(Ordering::SeqCst) && inner.pending_tasks.load(Ordering::SeqCst) == 0
}
pub(super) fn spin_for_work<const BLOCKING_QUEUE_CAPACITY: usize, const SPIN_LIMIT: usize>(
inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
worker_id: usize,
) -> bool {
for attempt in 0..SPIN_LIMIT {
core::hint::spin_loop();
let local = &inner.workers[worker_id];
if !local.queues.is_empty()
|| local.lifo_slot.state.load(Ordering::Relaxed) == 2
|| should_stop(inner)
{
return true;
}
if attempt % 32 == 0 && (has_stealable_work(inner, worker_id) || should_stop(inner)) {
return true;
}
}
false
}
fn has_stealable_work<const BLOCKING_QUEUE_CAPACITY: usize>(
inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
worker_id: usize,
) -> bool {
let worker_count = inner.workers.len();
const STEAL_PROBE_LIMIT: usize = 8;
let start = next_steal_start();
let probe_count = worker_count.min(STEAL_PROBE_LIMIT);
for offset in 0..probe_count {
let victim_index = start.wrapping_add(offset) % worker_count;
if victim_index == worker_id {
continue;
}
let victim = &inner.workers[victim_index];
if !victim.queues.is_empty() || victim.lifo_slot.state.load(Ordering::Relaxed) == 2 {
return true;
}
}
false
}
pub(super) fn wait_for_work<const BLOCKING_QUEUE_CAPACITY: usize>(
inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
worker_id: usize,
) {
wait_for_work_with_park(
&inner.idle_workers,
&inner.pending_tasks,
&inner.shutdown,
worker_id,
std::thread::park,
);
}
fn wait_for_work_with_park(
idle_workers: &IdleBitset,
pending_tasks: &AtomicUsize,
shutdown: &AtomicBool,
worker_id: usize,
mut park: impl FnMut(),
) {
loop {
idle_workers.set(worker_id);
if pending_tasks.load(Ordering::SeqCst) != 0 || shutdown.load(Ordering::SeqCst) {
break;
}
park();
}
idle_workers.clear(worker_id);
}
#[cfg(test)]
mod tests {
use std::{
cell::Cell,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
use super::{IdleBitset, wait_for_work_with_park};
#[test]
fn consumed_wake_republishes_worker_before_repark() {
let idle_workers = IdleBitset::new(1);
let pending_tasks = AtomicUsize::new(0);
let shutdown = AtomicBool::new(false);
let park_attempts = Cell::new(0);
wait_for_work_with_park(&idle_workers, &pending_tasks, &shutdown, 0, || {
let attempt = park_attempts.get();
park_attempts.set(attempt + 1);
assert_eq!(idle_workers.claim_one(1), Some(0));
if attempt == 1 {
shutdown.store(true, Ordering::SeqCst);
}
});
assert_eq!(park_attempts.get(), 2);
assert_eq!(idle_workers.claim_one(1), None);
}
}