use crate::garbage_node::GarbageNode;
use crate::padded_type::PaddedType;
use crate::task_batch::TaskBatch;
use crate::{TaskFnPointer, TaskFuture, TaskParamPointer};
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use std::thread::{self, Thread};
pub const NOT_IN_CRITICAL: usize = usize::MAX;
pub const EPOCH_MASK: usize = usize::MAX >> 1; pub const EPOCH_MASK_HALF: usize = EPOCH_MASK / 2;
pub struct Queue {
head: PaddedType<AtomicPtr<TaskBatch>>,
tail: PaddedType<AtomicPtr<TaskBatch>>,
global_epoch: PaddedType<AtomicUsize>,
local_epochs: Box<[PaddedType<AtomicUsize>]>,
threads: Box<[UnsafeCell<MaybeUninit<Thread>>]>,
shutdown: AtomicBool,
}
unsafe impl Sync for Queue {}
impl Queue {
pub fn new(worker_count: usize) -> Self {
fn noop(_: TaskParamPointer) {}
let anchor = TaskBatch::new(noop, NonNull::dangling(), 0, 0, TaskFuture::new(0));
let local_epochs: Box<[_]> = (0..worker_count)
.map(|_| PaddedType::new(AtomicUsize::new(NOT_IN_CRITICAL)))
.collect();
let threads: Box<[_]> = (0..worker_count)
.map(|_| UnsafeCell::new(MaybeUninit::uninit()))
.collect();
Queue {
head: PaddedType::new(AtomicPtr::new(anchor)),
tail: PaddedType::new(AtomicPtr::new(anchor)),
global_epoch: PaddedType::new(AtomicUsize::new(0)),
local_epochs,
threads,
shutdown: AtomicBool::new(false),
}
}
pub fn push_task_batch<T>(&self, task_fn: fn(&T), params: &[T]) -> TaskFuture {
if params.is_empty() {
return TaskFuture::new(0);
}
let future = TaskFuture::new(params.len());
let batch = TaskBatch::new(
unsafe { std::mem::transmute::<fn(&T), TaskFnPointer>(task_fn) },
NonNull::from(params).cast(),
std::mem::size_of::<T>(),
std::mem::size_of_val(params),
future.clone(),
);
self.push_and_notify(batch, params.len());
future
}
fn push_and_notify(&self, batch: *mut TaskBatch, mut count: usize) {
let prev_tail = self.tail.swap(batch, Ordering::AcqRel);
unsafe {
(*prev_tail).next.store(batch, Ordering::Release);
}
let num_workers = self.threads.len();
count = count.min(num_workers);
for i in 0..num_workers {
if self.local_epochs[i].load(Ordering::SeqCst) == NOT_IN_CRITICAL {
unsafe {
(*self.threads[i].get()).assume_init_ref().unpark();
count -= 1;
if count == 0 {
break;
}
}
}
}
}
pub fn get_next_batch(
&self,
worker_id: usize,
cached_local_epoch: &mut usize,
garbage_head: &mut *mut GarbageNode,
garbage_tail: &mut *mut GarbageNode,
) -> Option<(&TaskBatch, TaskParamPointer)> {
let global_epoch = self.global_epoch.load(Ordering::Relaxed) & EPOCH_MASK;
if *cached_local_epoch != global_epoch {
*cached_local_epoch = global_epoch;
self.local_epochs[worker_id].store(global_epoch, Ordering::SeqCst);
}
let mut current = self.head.load(Ordering::Acquire);
loop {
let batch = unsafe { &*current };
if let Some(param) = batch.claim_next_param() {
return Some((batch, param));
}
let next = batch.next.load(Ordering::Acquire);
if next.is_null() {
return None;
}
match self.head.compare_exchange_weak(
current,
next,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => {
self.on_consume_batch(current, garbage_head, garbage_tail);
current = next;
}
Err(new_head) => {
current = new_head;
}
}
}
}
fn on_consume_batch(
&self,
batch: *mut TaskBatch,
garbage_head: &mut *mut GarbageNode,
garbage_tail: &mut *mut GarbageNode,
) {
let fresh_epoch = self.global_epoch.load(Ordering::Relaxed) & EPOCH_MASK;
let garbage_node = GarbageNode::new(batch, fresh_epoch);
unsafe {
if garbage_head.is_null() {
*garbage_head = garbage_node;
} else {
(**garbage_tail).next = garbage_node;
}
*garbage_tail = garbage_node;
}
}
pub fn register_worker_thread(&self, worker_id: usize) {
unsafe {
(*self.threads[worker_id].get()).write(thread::current());
}
}
pub fn wait_for_work(&self, worker_id: usize, cached_local_epoch: &mut usize) -> bool {
loop {
if self.has_tasks() {
return true;
}
if self.is_shutdown() {
return false;
}
if *cached_local_epoch != NOT_IN_CRITICAL {
*cached_local_epoch = NOT_IN_CRITICAL;
self.local_epochs[worker_id].store(NOT_IN_CRITICAL, Ordering::SeqCst);
if self.has_tasks() {
return true;
}
}
thread::park();
}
}
pub fn advance_and_min_epoch(&self) -> usize {
let global_epoch = self
.global_epoch
.fetch_add(1, Ordering::Relaxed)
.wrapping_add(1);
let mut min_epoch = global_epoch & EPOCH_MASK;
for local_epoch in self.local_epochs.iter() {
let e = local_epoch.load(Ordering::SeqCst);
if e != NOT_IN_CRITICAL {
if min_epoch.wrapping_sub(e) & EPOCH_MASK < EPOCH_MASK_HALF {
min_epoch = e;
}
}
}
min_epoch
}
pub fn is_shutdown(&self) -> bool {
self.shutdown.load(Ordering::Acquire)
}
pub fn has_tasks(&self) -> bool {
let tail = self.tail.load(Ordering::Acquire);
unsafe { (&*tail).has_unclaimed_tasks() }
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Release);
self.threads.iter().for_each(|t| unsafe {
(*t.get()).assume_init_ref().unpark();
});
}
}
impl Drop for Queue {
fn drop(&mut self) {
for thread in self.threads.iter() {
unsafe {
(*thread.get()).assume_init_drop();
}
}
let mut current = self.head.load(Ordering::Relaxed);
while !current.is_null() {
let batch = unsafe { Box::from_raw(current) };
current = batch.next.load(Ordering::Relaxed);
}
}
}