use std::sync::{Arc, Mutex, PoisonError};
use std::task::{Context, Poll, Waker};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use std::vec::Vec;
use crate::core::line::{CompletionLine, Slot, reached};
use crate::core::reactor::Idle;
use crate::core::time::{Clock, DurNs, MonoNs, Timer};
#[derive(Clone, Copy, Debug)]
pub struct StdClock {
origin: Instant,
}
impl StdClock {
#[must_use]
pub fn new() -> Self {
Self {
origin: Instant::now(),
}
}
#[must_use]
pub fn from_origin(origin: Instant) -> Self {
Self { origin }
}
}
impl Default for StdClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for StdClock {
fn monotonic_ns(&self) -> MonoNs {
MonoNs(u64::try_from(self.origin.elapsed().as_nanos()).unwrap_or(u64::MAX))
}
}
impl Timer for StdClock {
async fn sleep_until(&self, at: MonoNs) {
let left = at.since(self.monotonic_ns());
if left.0 > 0 {
thread::sleep(Duration::from_nanos(left.0));
}
}
}
#[derive(Clone, Copy, Debug)]
enum LinePark {
#[cfg(feature = "wordwait")]
Word(crate::backend::wordwait::WordWait),
Yield,
}
#[derive(Clone, Copy, Debug)]
pub struct StdIdle {
clock: StdClock,
park: LinePark,
}
impl StdIdle {
#[must_use]
pub fn new(clock: StdClock) -> Self {
#[cfg(feature = "wordwait")]
if let Ok(w) = crate::backend::wordwait::WordWait::probe() {
return Self {
clock,
park: LinePark::Word(w),
};
}
Self {
clock,
park: LinePark::Yield,
}
}
#[cfg(feature = "wordwait")]
#[must_use]
pub fn with_word_wait(clock: StdClock, wait: crate::backend::wordwait::WordWait) -> Self {
Self {
clock,
park: LinePark::Word(wait),
}
}
#[must_use]
pub fn parks_in_hardware(&self) -> bool {
!matches!(self.park, LinePark::Yield)
}
}
impl Idle for StdIdle {
fn sleep_until(&mut self, at: MonoNs) {
let left = at.since(self.clock.monotonic_ns());
if left.0 > 0 {
thread::sleep(Duration::from_nanos(left.0));
}
}
fn park_line<R: Fn(&CompletionLine) -> bool>(
&mut self,
line: &CompletionLine,
ready: R,
bound: DurNs,
) -> bool {
match self.park {
#[cfg(feature = "wordwait")]
LinePark::Word(w) => w.park(line, ready, Duration::from_nanos(bound.0)),
LinePark::Yield => {
let until = Instant::now() + Duration::from_nanos(bound.0);
while !ready(line) {
if Instant::now() >= until {
return false;
}
thread::yield_now();
}
true
}
}
}
}
fn line_of<L: AsRef<CompletionLine>>(l: &Arc<L>) -> &CompletionLine {
(**l).as_ref()
}
struct Entry<L> {
id: u64,
line: Arc<L>,
slot: Slot,
target: u64,
waker: Waker,
}
struct Table<L> {
next: u64,
entries: Vec<Entry<L>>,
}
struct Shared<L> {
table: Mutex<Table<L>>,
stop: std::sync::atomic::AtomicBool,
}
impl<L> Shared<L> {
fn lock(&self) -> std::sync::MutexGuard<'_, Table<L>> {
self.table.lock().unwrap_or_else(PoisonError::into_inner)
}
}
pub struct Watcher<L: AsRef<CompletionLine> + Send + Sync + 'static> {
shared: Arc<Shared<L>>,
thread: Option<JoinHandle<()>>,
}
impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Watcher<L> {
#[must_use]
pub fn spawn(mut idle: StdIdle, park_bound: Duration) -> Self {
let shared = Arc::new(Shared {
table: Mutex::new(Table {
next: 0,
entries: Vec::new(),
}),
stop: std::sync::atomic::AtomicBool::new(false),
});
let s = Arc::clone(&shared);
let bound = DurNs(u64::try_from(park_bound.as_nanos()).unwrap_or(u64::MAX));
let thread = thread::spawn(move || {
while !s.stop.load(std::sync::atomic::Ordering::Acquire) {
let (done, first) = {
let mut t = s.lock();
let done: Vec<Entry<L>> = t
.entries
.extract_if(.., |x| reached(line_of(&x.line), x.slot, x.target))
.collect();
let first = t.entries.first().map(|x| (Arc::clone(&x.line), x.slot, x.target));
(done, first)
};
for e in done {
e.waker.wake();
}
match first {
None => thread::park(),
Some((line, slot, target)) => {
let _ = idle.park_line(
line_of(&line),
|l| reached(l, slot, target),
bound,
);
}
}
}
});
Self {
shared,
thread: Some(thread),
}
}
#[must_use]
pub fn wait(&self, line: Arc<L>, slot: Slot, target: u64) -> Watch<L> {
Watch {
shared: Arc::clone(&self.shared),
thread: self.thread.as_ref().map(|t| t.thread().clone()),
line,
slot,
target,
id: None,
}
}
}
impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Drop for Watcher<L> {
fn drop(&mut self) {
self.shared
.stop
.store(true, std::sync::atomic::Ordering::Release);
if let Some(t) = self.thread.take() {
t.thread().unpark();
let _ = t.join();
}
}
}
pub struct Watch<L: AsRef<CompletionLine> + Send + Sync + 'static> {
shared: Arc<Shared<L>>,
thread: Option<thread::Thread>,
line: Arc<L>,
slot: Slot,
target: u64,
id: Option<u64>,
}
impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Watch<L> {
fn reached(&self) -> bool {
reached(line_of(&self.line), self.slot, self.target)
}
fn deregister(&mut self) {
let Some(id) = self.id.take() else { return };
let removed = {
let mut t = self.shared.lock();
t.entries
.iter()
.position(|x| x.id == id)
.map(|i| t.entries.swap_remove(i))
};
drop(removed);
}
}
impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Future for Watch<L> {
type Output = ();
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = self.get_mut();
if this.reached() {
this.deregister();
return Poll::Ready(());
}
{
let mut t = this.shared.lock();
let mine = this.id.and_then(|id| t.entries.iter_mut().find(|x| x.id == id));
match mine {
Some(x) => x.waker.clone_from(cx.waker()),
None => {
let id = t.next;
t.next = t.next.wrapping_add(1);
t.entries.push(Entry {
id,
line: Arc::clone(&this.line),
slot: this.slot,
target: this.target,
waker: cx.waker().clone(),
});
this.id = Some(id);
}
}
}
if let Some(t) = &this.thread {
t.unpark();
}
if this.reached() {
this.deregister();
return Poll::Ready(());
}
Poll::Pending
}
}
impl<L: AsRef<CompletionLine> + Send + Sync + 'static> Drop for Watch<L> {
fn drop(&mut self) {
self.deregister();
}
}