use crate::linked_list::{LinkedList, Node};
use crate::loom::sync::atomic::{AtomicIsize, Ordering};
use crate::loom::sync::Mutex;
use crate::loom::thread;
use crate::parker::Parker;
use crate::tag::Tag;
use std::mem;
use std::ptr;
pub(super) type Prelude = dyn Fn() + Send + 'static;
pub(super) struct Shared {
modifiers: AtomicIsize,
queue: Mutex<LinkedList<Entry>>,
parker: Parker,
}
impl Shared {
pub(super) fn new() -> Self {
Self {
modifiers: AtomicIsize::new(0),
queue: Mutex::new(LinkedList::new()),
parker: Parker::new(),
}
}
pub(super) fn lock_queue(&self) -> Option<ModifierGuard<'_>> {
let value = self.modifiers.fetch_add(1, Ordering::SeqCst);
if value == isize::MAX {
eprintln!("ste: modifiers invariant breached, aborting");
std::process::abort();
}
if value < 0 {
self.modifiers.fetch_sub(1, Ordering::SeqCst);
return None;
}
Some(ModifierGuard {
modifiers: &self.modifiers,
})
}
unsafe fn panic_join(&self) {
let modifiers = self.modifiers.fetch_add(isize::MIN, Ordering::SeqCst);
debug_assert!(modifiers >= 0);
let mut local = self.queue.lock().unwrap().steal();
release_local_queue(&mut local);
while self.modifiers.load(Ordering::Acquire) != isize::MIN {
thread::yield_now();
}
let mut local = self.queue.lock().unwrap().steal();
release_local_queue(&mut local);
}
pub(super) unsafe fn schedule_in_place(&self, parker: ptr::NonNull<Parker>, entry: Entry) {
let mut node = Node::new(entry);
let first = {
let _guard = match self.lock_queue() {
Some(guard) => guard,
None => panic!("background thread ended"),
};
self.queue
.lock()
.unwrap()
.push_front(ptr::NonNull::from(&mut node))
};
if first {
self.parker.unpark();
}
parker.as_ref().park();
}
pub(super) fn outer_join(&self) {
self.modifiers.fetch_add(isize::MIN, Ordering::SeqCst);
self.parker.unpark();
}
}
pub(super) struct ModifierGuard<'a> {
modifiers: &'a AtomicIsize,
}
impl Drop for ModifierGuard<'_> {
fn drop(&mut self) {
self.modifiers.fetch_sub(1, Ordering::SeqCst);
}
}
pub(super) fn run(prelude: Option<Box<Prelude>>, shared: ptr::NonNull<Shared>) {
unsafe {
let shared = shared.as_ref();
let tag = Tag(shared as *const _ as usize);
if let Some(prelude) = prelude {
let guard = PoisonGuard { shared };
prelude();
mem::forget(guard);
}
while let Some(guard) = shared.lock_queue() {
let mut local = shared.queue.lock().unwrap().steal();
drop(guard);
if local.is_empty() {
shared.parker.park();
continue;
}
while let Some(mut entry) = local.pop_front() {
let entry = &mut entry.as_mut().value;
entry.task.as_mut()(tag);
entry.parker.as_ref().unpark();
}
}
}
struct PoisonGuard<'a> {
shared: &'a Shared,
}
impl Drop for PoisonGuard<'_> {
fn drop(&mut self) {
unsafe {
self.shared.panic_join();
}
}
}
}
#[derive(Debug)]
pub(super) struct Entry {
task: ptr::NonNull<dyn FnMut(Tag) + Send + 'static>,
parker: ptr::NonNull<Parker>,
}
impl Entry {
pub(super) unsafe fn new(
task: &mut (impl FnMut(Tag) + Send),
parker: ptr::NonNull<Parker>,
) -> Self {
Self {
task: ptr::NonNull::new_unchecked(mem::transmute::<&mut (dyn FnMut(Tag) + Send), _>(
task,
)),
parker,
}
}
unsafe fn release(&self) {
self.parker.as_ref().unpark();
}
}
unsafe fn release_local_queue(queue: &mut LinkedList<Entry>) {
while let Some(entry) = queue.pop_back() {
entry.as_ref().value.release();
}
}