use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use std::task::Waker;
use std::thread::JoinHandle;
use orbit_core::process::ProcessOwner;
use crate::lock_unpoisoned;
#[derive(Clone, Copy)]
pub(crate) struct Interest {
pub(crate) direction: usize,
pub(crate) writer: bool,
pub(crate) reset: bool,
}
impl Interest {
fn index(self) -> usize {
self.direction * 3
+ if self.reset { 2 } else { usize::from(self.writer) }
}
}
#[derive(Default)]
struct SlotWakers {
wakers: [Option<Waker>; 6],
}
pub(crate) struct Registry {
slots: Box<[Mutex<SlotWakers>]>,
offer: Mutex<Option<Waker>>,
}
impl Registry {
pub(crate) fn new(total_slots: usize) -> Self {
Self {
slots: (0..total_slots)
.map(|_| Mutex::new(SlotWakers::default()))
.collect(),
offer: Mutex::new(None),
}
}
pub(crate) fn register(
&self,
slot: usize,
interest: Interest,
waker: &Waker,
) {
let mut entry = lock_unpoisoned(&self.slots[slot]);
let place = &mut entry.wakers[interest.index()];
match place {
Some(existing) if existing.will_wake(waker) => {}
_ => *place = Some(waker.clone()),
}
}
pub(crate) fn register_offer(
&self,
waker: &Waker,
) {
let mut place = lock_unpoisoned(&self.offer);
match &*place {
Some(existing) if existing.will_wake(waker) => {}
_ => *place = Some(waker.clone()),
}
}
pub(crate) fn wake(
&self,
slot: usize,
) {
let taken = {
let mut entry = lock_unpoisoned(&self.slots[slot]);
std::mem::take(&mut entry.wakers)
};
for waker in taken.into_iter().flatten() {
waker.wake();
}
}
pub(crate) fn wake_offer(&self) {
if let Some(waker) = lock_unpoisoned(&self.offer).take() {
waker.wake();
}
}
pub(crate) fn clear(&self) {
for slot in &self.slots {
lock_unpoisoned(slot).wakers = Default::default();
}
lock_unpoisoned(&self.offer).take();
}
}
pub(crate) trait Doorstep: Send + Sync + 'static {
fn generation(&self) -> &AtomicU32;
fn driver_waiters(&self) -> &AtomicU32;
fn drain(&self);
}
pub(crate) struct Driver {
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
owner: ProcessOwner,
}
impl Driver {
pub(crate) fn start<T: Doorstep>(
target: *const T,
name: String,
) -> std::io::Result<Self> {
let owner = ProcessOwner::current()?;
let stop = Arc::new(AtomicBool::new(false));
let thread_stop = Arc::clone(&stop);
let target = SendPtr(target);
let thread =
std::thread::Builder::new().name(name).spawn(move || {
let target = target;
let table = unsafe { &*target.0 };
run(table, &thread_stop, crate::wait_on);
})?;
Ok(Self { stop, thread: Some(thread), owner })
}
pub(crate) fn stop(
&mut self,
generation: &AtomicU32,
) {
if !self.owner.is_current() {
return;
}
signal_stop(&self.stop, generation);
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
struct SendPtr<T>(*const T);
unsafe impl<T: Sync> Send for SendPtr<T> {}
fn signal_stop(
stop: &AtomicBool,
generation: &AtomicU32,
) {
stop.store(true, Ordering::Release);
generation.fetch_add(1, Ordering::SeqCst);
crate::wake_on(generation);
}
fn run<T: Doorstep>(
table: &T,
stop: &AtomicBool,
wait: impl Fn(&AtomicU32, u32) -> crate::Result<()>,
) {
let generation = table.generation();
let waiters = table.driver_waiters();
while !stop.load(Ordering::Acquire) {
let seen = generation.load(Ordering::SeqCst);
table.drain();
waiters.fetch_add(1, Ordering::SeqCst);
let now = generation.load(Ordering::SeqCst);
let result = if now == seen && !stop.load(Ordering::Acquire) {
wait(generation, seen)
} else {
Ok(())
};
waiters.fetch_sub(1, Ordering::SeqCst);
if result.is_err() {
break;
}
}
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "freebsd", target_os = "macos")
))]
#[path = "../tests/unit/wake.rs"]
mod tests;