use super::{mailbox::Mailbox, pool::Pool, task::Ready};
use std::{mem, sync::Arc, thread};
cfg_if::cfg_if! {
if #[cfg(feature = "loom")] {
use loom::sync::atomic::{AtomicU64, AtomicUsize, Ordering, fence};
} else {
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering, fence};
}
}
pub struct Idle {
set: AtomicU64,
searching: AtomicUsize,
}
impl Idle {
#[allow(clippy::missing_const_for_fn)]
pub fn new() -> Self {
Self {
set: AtomicU64::new(0),
searching: AtomicUsize::new(0),
}
}
pub fn notify(&self, mailboxes: &[Arc<Mailbox>]) {
loop {
if self.searching.load(Ordering::Relaxed) != 0 {
return;
}
let idle = self.set.load(Ordering::Relaxed);
if idle == 0 {
return;
}
if self
.searching
.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed)
.is_err()
{
return;
}
if self.claim(idle, mailboxes) {
return;
}
if !self.undo_claim() {
return;
}
}
}
pub fn notify_global(&self, mailboxes: &[Arc<Mailbox>]) {
loop {
let idle = self.set.load(Ordering::Relaxed);
if idle == 0 {
return;
}
self.searching.fetch_add(1, Ordering::Relaxed);
if self.claim(idle, mailboxes) {
return;
}
if !self.undo_claim() {
return;
}
}
}
fn undo_claim(&self) -> bool {
#[cfg(test)]
tests::at_wake_point(tests::WakePoint::AfterFailedClaim);
if self.searching.fetch_sub(1, Ordering::Relaxed) != 1 {
return false;
}
fence(Ordering::SeqCst);
true
}
fn claim(&self, mut idle: u64, mailboxes: &[Arc<Mailbox>]) -> bool {
#[cfg(test)]
tests::at_wake_point(tests::WakePoint::BeforeClaim);
while idle != 0 {
let index = idle.trailing_zeros();
match self.set.compare_exchange_weak(
idle,
idle & !(1 << index),
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => {
#[cfg(test)]
tests::at_wake_point(tests::WakePoint::AfterClaim);
mailboxes[index as usize].waker.wake();
return true;
}
Err(actual) => idle = actual,
}
}
false
}
}
impl Drop for Idle {
fn drop(&mut self) {
if thread::panicking() {
return;
}
assert_eq!(
self.searching.load(Ordering::Relaxed),
0,
"every search count must be returned before the idle state drops"
);
assert_eq!(
self.set.load(Ordering::Relaxed),
0,
"every idle bit must be withdrawn before the idle state drops"
);
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Park {
Wait,
Run,
Search,
}
pub struct Searcher<'a> {
pool: &'a Pool,
index: u32,
published: bool,
searching: bool,
rng: u32,
}
impl<'a> Searcher<'a> {
pub fn new(pool: &'a Pool, index: u32) -> Self {
assert!((index as usize) < pool.workers());
Self {
pool,
index,
published: false,
searching: false,
rng: index + 1,
}
}
#[must_use]
pub fn search(&mut self, ready: &mut Ready) -> bool {
let pool = self.pool;
if pool.workers() == 1 || (!self.searching && !self.begin()) {
return false;
}
self.rng ^= self.rng << 13;
self.rng ^= self.rng >> 17;
self.rng ^= self.rng << 5;
let start = self.rng as usize % pool.workers();
pool.steal(self.index, start, ready)
}
fn begin(&mut self) -> bool {
let searching = self.pool.idle().searching.load(Ordering::Relaxed);
if 2 * searching >= self.pool.workers() {
return false;
}
self.pool.idle().searching.fetch_add(1, Ordering::Relaxed);
self.searching = true;
true
}
#[inline]
pub fn leave_search(&mut self) {
if self.searching {
self.end_search();
}
}
#[inline(never)]
fn end_search(&mut self) {
assert!(
!self.published,
"a publication must end before leaving a search"
);
self.searching = false;
let pool = self.pool;
if pool.idle().searching.fetch_sub(1, Ordering::Release) == 1 {
pool.kick();
}
}
#[must_use]
pub fn prepare_park(&mut self, ready: &Ready) -> Park {
assert!(
!self.published,
"a publication must end before the next one"
);
assert!(ready.is_empty(), "run queue must be empty to park");
let pool = self.pool;
pool.idle().set.fetch_or(1 << self.index, Ordering::Relaxed);
self.published = true;
let last = mem::take(&mut self.searching)
&& pool.idle().searching.fetch_sub(1, Ordering::Relaxed) == 1;
fence(Ordering::SeqCst);
if pool.has_global() {
if !self.withdraw() && last {
pool.kick();
}
return Park::Run;
}
if pool.idle().searching.load(Ordering::Relaxed) != 0 {
return Park::Wait;
}
if !pool.has_stealable(self.index)
|| pool
.idle()
.searching
.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed)
.is_err()
{
return Park::Wait;
}
#[cfg(test)]
tests::at_wake_point(tests::WakePoint::BeforeParkSearch);
if self.withdraw() {
pool.idle().searching.fetch_sub(1, Ordering::Relaxed);
}
self.searching = true;
Park::Search
}
#[must_use]
pub fn publish_and_look(&mut self, ready: &mut Ready) -> bool {
loop {
match self.prepare_park(ready) {
Park::Wait => return false,
Park::Run => return true,
Park::Search => {
if self.search(ready) {
return true;
}
}
}
}
}
pub fn finish_park(&mut self) {
self.withdraw();
}
fn withdraw(&mut self) -> bool {
assert!(self.published, "withdrawal must end a live publication");
self.published = false;
let bit = 1 << self.index;
let claimed = self.pool.idle().set.fetch_and(!bit, Ordering::Acquire) & bit == 0;
if claimed {
assert!(
!self.searching,
"a claimed worker must not already hold a search count"
);
self.searching = true;
}
claimed
}
}
impl Drop for Searcher<'_> {
fn drop(&mut self) {
if self.published {
self.withdraw();
}
self.leave_search();
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::iouring::{
pool::{
HALF,
tests::{pool, push, ready, task},
},
queue::{CAPACITY, STEALABLE},
task::{Task, tests::queue},
tasks::Tasks,
};
use std::{cell::RefCell, rc::Rc};
impl Idle {
pub fn is_idle(&self, index: u32) -> bool {
self.set.load(Ordering::Relaxed) & (1 << index) != 0
}
pub fn search_count(&self) -> usize {
self.searching.load(Ordering::Relaxed)
}
}
impl<'a> Searcher<'a> {
pub const fn pool(&self) -> &'a Pool {
self.pool
}
pub const fn index(&self) -> u32 {
self.index
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WakePoint {
BeforeClaim,
AfterClaim,
AfterFailedClaim,
BeforeParkSearch,
}
type WakeHook = (WakePoint, Box<dyn FnOnce()>);
thread_local! {
static WAKE_HOOKS: RefCell<Vec<WakeHook>> = const { RefCell::new(Vec::new()) };
}
pub fn on_wake_point(point: WakePoint, hook: impl FnOnce() + 'static) {
WAKE_HOOKS.with(|hooks| hooks.borrow_mut().push((point, Box::new(hook))));
}
pub fn at_wake_point(point: WakePoint) {
let hook = WAKE_HOOKS.with(|hooks| {
let mut hooks = hooks.borrow_mut();
hooks
.iter()
.position(|hook| hook.0 == point)
.map(|at| hooks.remove(at).1)
});
if let Some(hook) = hook {
hook();
}
}
fn enqueue(pool: &Pool, ready: &mut Ready, set: &Tasks, count: usize) -> Vec<Task> {
(0..count)
.map(|_| {
let (task, runnable) = task(set);
pool.enqueue(ready, runnable);
task
})
.collect()
}
fn signalled(pool: &Pool, index: u32) -> bool {
pool.mailbox(index).waker.signalled()
}
fn consume_signal(pool: &Pool, mode: usize) {
let waker = &pool.mailbox(1).waker;
assert!(waker.signalled());
match mode {
0 => assert!(waker.park_idle(0, None).is_none()),
1 => assert!(!waker.arm(0).still_idle()),
_ => waker.consume_signal(),
}
assert!(!waker.signalled());
}
fn release(pool: &Pool, readies: &mut [&mut Ready], tasks: Vec<Task>) {
while let Some(runnable) = pool.pop() {
runnable.discard();
}
for ready in readies {
ready.discard();
}
for task in tasks {
task.clear();
}
}
type Part = Rc<RefCell<(Searcher<'static>, Ready)>>;
fn leaked(workers: usize) -> (&'static Pool, Vec<Part>) {
let pool: &'static Pool = Box::leak(Box::new(pool(workers)));
let parts = (0..workers as u32)
.map(|index| {
Rc::new(RefCell::new((
Searcher::new(pool, index),
ready(pool, index),
)))
})
.collect();
(pool, parts)
}
#[test]
fn test_global_pushes_each_wake_a_worker_lowest_first() {
let pool = pool(4);
let set = Tasks::new(1);
let mut readies: Vec<Ready> = (0..4).map(|index| ready(&pool, index)).collect();
let mut parked: Vec<Searcher<'_>> =
(1..4).map(|index| Searcher::new(&pool, index)).collect();
for (searcher, ready) in parked.iter_mut().zip(&readies[1..]) {
assert_eq!(searcher.prepare_park(ready), Park::Wait);
}
let mut tasks = vec![push(&pool, &set)];
assert!(!pool.idle().is_idle(1) && pool.idle().is_idle(2));
assert!(signalled(&pool, 1) && !signalled(&pool, 2));
assert_eq!(pool.idle().search_count(), 1);
tasks.push(push(&pool, &set));
assert!(!pool.idle().is_idle(2) && signalled(&pool, 2));
assert_eq!(pool.idle().search_count(), 2);
tasks.extend(enqueue(&pool, &mut readies[0], &set, STEALABLE));
assert!(pool.idle().is_idle(3) && !signalled(&pool, 3));
parked[0].finish_park();
parked[1].finish_park();
assert!(parked[0].searching && parked[1].searching);
parked[0].leave_search();
assert!(pool.idle().is_idle(3));
parked[1].leave_search();
assert!(!pool.idle().is_idle(3) && signalled(&pool, 3));
parked[2].finish_park();
parked[2].leave_search();
assert_eq!(pool.idle().search_count(), 0);
while let Some(runnable) = pool.pop() {
runnable.discard();
}
let mut zero = Searcher::new(&pool, 0);
readies[0].discard();
assert_eq!(zero.prepare_park(&readies[0]), Park::Wait);
zero.finish_park();
tasks.push(push(&pool, &set));
assert!(!signalled(&pool, 0));
assert_eq!(pool.idle().search_count(), 0);
drop((parked, zero));
let mut readies: Vec<&mut Ready> = readies.iter_mut().collect();
release(&pool, &mut readies, tasks);
}
#[test]
fn test_kick_needs_a_stealable_backlog() {
let pool = pool(2);
let set = Tasks::new(1);
let (mut zero, mut one) = (ready(&pool, 0), ready(&pool, 1));
let mut parked = Searcher::new(&pool, 1);
assert_eq!(parked.prepare_park(&one), Park::Wait);
let mut tasks = enqueue(&pool, &mut zero, &set, STEALABLE - 1);
assert!(pool.idle().is_idle(1) && !signalled(&pool, 1));
tasks.extend(enqueue(&pool, &mut zero, &set, 1));
assert!(!pool.idle().is_idle(1) && signalled(&pool, 1));
parked.finish_park();
assert!(parked.searching);
assert!(parked.search(&mut one));
parked.leave_search();
assert_eq!(pool.idle().search_count(), 0);
drop(parked);
release(&pool, &mut [&mut zero, &mut one], tasks);
}
#[test]
fn test_searches_started_in_turn_stop_at_half_the_workers() {
let pool = pool(4);
let mut readies: Vec<Ready> = (0..4).map(|index| ready(&pool, index)).collect();
let mut searchers: Vec<Searcher<'_>> =
(0..4).map(|index| Searcher::new(&pool, index)).collect();
let mut held = Vec::new();
for (searcher, ready) in searchers.iter_mut().zip(&mut readies) {
assert!(!searcher.search(ready));
held.push(searcher.searching);
}
assert_eq!(held, [true, true, false, false]);
assert_eq!(pool.idle().search_count(), 2);
for searcher in &mut searchers {
searcher.leave_search();
}
assert_eq!(pool.idle().search_count(), 0);
}
#[test]
fn test_cap_refused_parker_waits_for_the_searchers() {
let pool = pool(4);
let set = Tasks::new(1);
let mut readies: Vec<Ready> = (0..4).map(|index| ready(&pool, index)).collect();
let mut searchers: Vec<Searcher<'_>> =
(0..3).map(|index| Searcher::new(&pool, index)).collect();
for (searcher, ready) in searchers.iter_mut().zip(&mut readies) {
assert!(!searcher.search(ready));
}
assert!(searchers[0].searching && searchers[1].searching && !searchers[2].searching);
let tasks = enqueue(&pool, &mut readies[3], &set, STEALABLE);
assert_eq!(searchers[2].prepare_park(&readies[2]), Park::Wait);
assert!(pool.idle().is_idle(2) && !signalled(&pool, 2));
searchers[0].leave_search();
assert!(pool.idle().is_idle(2));
searchers[1].leave_search();
assert!(!pool.idle().is_idle(2) && signalled(&pool, 2));
searchers[2].finish_park();
assert!(searchers[2].search(&mut readies[2]));
searchers[2].leave_search();
assert_eq!(pool.idle().search_count(), 0);
drop(searchers);
let mut readies: Vec<&mut Ready> = readies.iter_mut().collect();
release(&pool, &mut readies, tasks);
}
#[test]
fn test_parker_seeing_a_backlog_delegates_or_searches() {
for held in [true, false] {
let pool = pool(3);
let set = Tasks::new(1);
let (mut zero, mut one, mut two) = (ready(&pool, 0), ready(&pool, 1), ready(&pool, 2));
let mut searcher = Searcher::new(&pool, 0);
let mut parker = Searcher::new(&pool, 1);
if held {
assert!(!searcher.search(&mut zero));
}
let mut tasks = Vec::new();
for _ in 0..STEALABLE {
let (task, runnable) = task(&set);
queue(&mut two, runnable);
tasks.push(task);
}
let park = parker.prepare_park(&one);
if held {
assert_eq!(park, Park::Wait);
assert!(pool.idle().is_idle(1));
searcher.leave_search();
assert!(!pool.idle().is_idle(1) && signalled(&pool, 1));
parker.finish_park();
} else {
assert_eq!(park, Park::Search);
assert!(!pool.idle().is_idle(1));
}
assert!(parker.searching);
assert!(parker.search(&mut one));
parker.leave_search();
assert_eq!(pool.idle().search_count(), 0);
drop((searcher, parker));
release(&pool, &mut [&mut zero, &mut one, &mut two], tasks);
}
}
#[test]
fn test_run_after_the_last_count_hands_the_duty_on() {
let pool = pool(4);
let set = Tasks::new(1);
let (mut r0, mut r1, mut r2, mut r3) = (
ready(&pool, 0),
ready(&pool, 1),
ready(&pool, 2),
ready(&pool, 3),
);
let mut zero = Searcher::new(&pool, 0);
let mut one = Searcher::new(&pool, 1);
let mut two = Searcher::new(&pool, 2);
assert_eq!(zero.prepare_park(&r0), Park::Wait);
assert_eq!(one.prepare_park(&r1), Park::Wait);
assert!(!two.search(&mut r2));
assert_eq!(pool.idle().search_count(), 1);
let tasks = enqueue(&pool, &mut r3, &set, CAPACITY + 1);
assert!(!pool.idle().is_idle(0) && signalled(&pool, 0));
assert!(pool.idle().is_idle(1) && !signalled(&pool, 1));
assert_eq!(pool.idle().search_count(), 2);
zero.finish_park();
pool.take(&mut r0).unwrap().discard();
zero.leave_search();
assert!(pool.idle().is_idle(1) && !signalled(&pool, 1));
assert_eq!(pool.idle().search_count(), 1);
assert_eq!(two.prepare_park(&r2), Park::Run);
assert!(!two.searching);
assert!(!pool.idle().is_idle(1) && signalled(&pool, 1));
assert_eq!(pool.idle().search_count(), 1);
one.finish_park();
assert!(one.searching);
assert!(one.search(&mut r1));
one.leave_search();
assert_eq!(pool.idle().search_count(), 0);
drop((zero, one, two));
release(&pool, &mut [&mut r0, &mut r1, &mut r2, &mut r3], tasks);
}
#[test]
fn test_parker_without_room_waits_for_the_claiming_thief() {
let pool = pool(3);
let set = Tasks::new(1);
let (mut zero, mut one, mut two) = (ready(&pool, 0), ready(&pool, 1), ready(&pool, 2));
let mut parker = Searcher::new(&pool, 0);
let mut thief = Searcher::new(&pool, 1);
let mut tasks = Vec::new();
for _ in 0..CAPACITY {
let (task, runnable) = task(&set);
queue(&mut zero, runnable);
tasks.push(task);
}
assert!(thief.begin());
let claim = pool.queue(0).hold_claim(HALF as u32);
while let Some(runnable) = zero.pop() {
runnable.discard();
}
tasks.extend(enqueue(&pool, &mut two, &set, STEALABLE));
assert_eq!(zero.room(), 0);
assert_eq!(parker.prepare_park(&zero), Park::Wait);
assert!(pool.idle().is_idle(0) && !signalled(&pool, 0));
assert_eq!(pool.idle().search_count(), 1);
for runnable in pool.queue(0).finish_claim(claim) {
runnable.discard();
}
thief.leave_search();
assert!(!pool.idle().is_idle(0) && signalled(&pool, 0));
parker.finish_park();
assert!(parker.search(&mut zero));
parker.leave_search();
assert_eq!(pool.idle().search_count(), 0);
release(&pool, &mut [&mut zero, &mut one, &mut two], tasks);
}
#[test]
fn test_dropping_a_searcher_restores_the_idle_state() {
let pool = pool(2);
let set = Tasks::new(1);
let (mut zero, one) = (ready(&pool, 0), ready(&pool, 1));
drop(Searcher::new(&pool, 1));
assert_eq!(pool.idle().search_count(), 0);
let mut parker = Searcher::new(&pool, 1);
assert_eq!(parker.prepare_park(&one), Park::Wait);
drop(parker);
assert!(!pool.idle().is_idle(1));
let mut parker = Searcher::new(&pool, 1);
assert_eq!(parker.prepare_park(&one), Park::Wait);
let task = push(&pool, &set);
assert!(!pool.idle().is_idle(1));
assert_eq!(pool.idle().search_count(), 1);
drop(parker);
assert_eq!(pool.idle().search_count(), 0);
let mut searcher = Searcher::new(&pool, 0);
assert!(!searcher.search(&mut zero));
assert_eq!(pool.idle().search_count(), 1);
drop(searcher);
assert_eq!(pool.idle().search_count(), 0);
drop((zero, one));
release(&pool, &mut [], vec![task]);
}
#[test]
fn test_kick_whose_claim_fails_looks_again() {
let (pool, parts) = leaked(3);
let set = Tasks::new(1);
{
let (candidate, ready) = &mut *parts[1].borrow_mut();
assert_eq!(candidate.prepare_park(ready), Park::Wait);
}
on_wake_point(WakePoint::BeforeClaim, {
let part = parts[1].clone();
move || part.borrow_mut().0.finish_park()
});
on_wake_point(WakePoint::AfterFailedClaim, {
let part = parts[2].clone();
move || {
let (later, ready) = &mut *part.borrow_mut();
assert_eq!(later.prepare_park(ready), Park::Wait);
}
});
let tasks = enqueue(pool, &mut parts[0].borrow_mut().1, &set, STEALABLE);
assert!(!pool.idle().is_idle(2) && signalled(pool, 2));
let (later, ready) = &mut *parts[2].borrow_mut();
later.finish_park();
assert!(later.search(ready));
later.leave_search();
assert_eq!(pool.idle().search_count(), 0);
assert!((0..3).all(|index| !pool.idle().is_idle(index)));
release(pool, &mut [&mut parts[0].borrow_mut().1, ready], tasks);
}
#[test]
fn test_global_push_whose_claim_fails_looks_again() {
let (pool, parts) = leaked(4);
let set = Rc::new(Tasks::new(1));
let kicked = Rc::new(RefCell::new(Vec::new()));
{
let (candidate, ready) = &mut *parts[1].borrow_mut();
assert_eq!(candidate.prepare_park(ready), Park::Wait);
}
on_wake_point(WakePoint::BeforeClaim, {
let part = parts[1].clone();
move || part.borrow_mut().0.finish_park()
});
on_wake_point(WakePoint::AfterFailedClaim, {
let owner = parts[0].clone();
let later = parts[2].clone();
let set = set.clone();
let kicked = kicked.clone();
move || {
pool.pop().unwrap().discard();
kicked.borrow_mut().extend(enqueue(
pool,
&mut owner.borrow_mut().1,
&set,
STEALABLE,
));
let (later, ready) = &mut *later.borrow_mut();
assert_eq!(later.prepare_park(ready), Park::Wait);
}
});
let pushed = push(pool, &set);
assert!(!pool.idle().is_idle(2) && signalled(pool, 2));
let (later, ready) = &mut *parts[2].borrow_mut();
later.finish_park();
assert!(later.search(ready));
later.leave_search();
assert_eq!(pool.idle().search_count(), 0);
assert!((0..4).all(|index| !pool.idle().is_idle(index)));
let mut tasks = kicked.take();
tasks.push(pushed);
release(pool, &mut [&mut parts[0].borrow_mut().1, ready], tasks);
}
#[test]
fn test_global_claim_of_a_worker_about_to_search_leaves_one_count() {
let (pool, parts) = leaked(2);
let set = Tasks::new(1);
let mut tasks = Vec::new();
for _ in 0..STEALABLE {
let (task, runnable) = task(&set);
queue(&mut parts[1].borrow_mut().1, runnable);
tasks.push(task);
}
let (pushed, runnable) = task(&set);
on_wake_point(WakePoint::BeforeParkSearch, move || {
assert!(pool.push(runnable).is_ok());
});
let (searcher, ready) = &mut *parts[0].borrow_mut();
assert_eq!(searcher.prepare_park(ready), Park::Search);
assert!(signalled(pool, 0));
assert_eq!(pool.idle().search_count(), 1);
assert!(searcher.search(ready));
searcher.leave_search();
assert_eq!(pool.idle().search_count(), 0);
assert!((0..2).all(|index| !pool.idle().is_idle(index)));
tasks.push(pushed);
release(pool, &mut [ready, &mut parts[1].borrow_mut().1], tasks);
}
#[test]
fn test_late_signal_of_a_claim_brings_no_count() {
for mode in 0..3 {
let (pool, parts) = leaked(2);
{
let (parker, ready) = &mut *parts[1].borrow_mut();
assert_eq!(parker.prepare_park(ready), Park::Wait);
}
on_wake_point(WakePoint::AfterClaim, {
let part = parts[1].clone();
move || {
assert_eq!(pool.idle().search_count(), 1);
assert!(!pool.idle().is_idle(1));
pool.mailbox(1).waker.wake();
consume_signal(pool, mode);
let (parker, ready) = &mut *part.borrow_mut();
parker.finish_park();
assert!(parker.searching);
parker.leave_search();
assert_eq!(pool.idle().search_count(), 0);
assert_eq!(parker.prepare_park(ready), Park::Wait);
}
});
pool.idle().notify_global(pool.mailboxes());
assert!(pool.idle().is_idle(1));
consume_signal(pool, mode);
{
let (parker, ready) = &mut *parts[1].borrow_mut();
parker.finish_park();
assert!(!parker.searching);
assert_eq!(pool.idle().search_count(), 0);
assert_eq!(parker.prepare_park(ready), Park::Wait);
}
pool.idle().notify_global(pool.mailboxes());
consume_signal(pool, mode);
let (parker, _) = &mut *parts[1].borrow_mut();
parker.finish_park();
assert!(parker.searching);
parker.leave_search();
assert_eq!(pool.idle().search_count(), 0);
assert!((0..2).all(|index| !pool.idle().is_idle(index)));
}
}
}