use alloc::{boxed::Box, sync::Arc, task::Wake};
use core::{
future::{Future, IntoFuture, poll_fn},
pin::{Pin, pin},
sync::atomic::{AtomicU64, Ordering},
task::{Context, Poll, Waker},
};
use ax_runtime::hal::time::TimeValue;
use ax_std::os::arceos::task as scheduler;
use axpoll::{IoEvents, PollRegistration, PollSource, RegistrationMode};
use axpoll_set::PollSet;
use super::{Elapsed, TimerFuture, UserTaskRef};
use crate::time::{ClockDeadline, ClockSnapshot};
static WALL_CLOCK_CHANGE_GENERATION: AtomicU64 = AtomicU64::new(0);
static WALL_CLOCK_CHANGE_EVENT: PollSet = PollSet::new();
pub async fn timeout_at_wall<F: IntoFuture>(
deadline: Option<TimeValue>,
future: F,
) -> Result<F::Output, Elapsed> {
let Some(deadline) = deadline else {
return Ok(future.await);
};
let mut future = pin!(future.into_future());
loop {
let mut changed = pin!(ClockChangeListener::new());
let mut timer = pin!(TimerFuture::new(wall_deadline_to_monotonic(deadline)));
let result = poll_fn(|context| {
if let Poll::Ready(output) = future.as_mut().poll(context) {
return Poll::Ready(Ok(Some(output)));
}
if changed.as_mut().poll(context).is_ready() {
return Poll::Ready(Ok(None));
}
if timer.as_mut().poll(context).is_ready() {
return Poll::Ready(if ClockDeadline::Realtime(deadline).lag().is_some() {
Err(Elapsed)
} else {
Ok(None)
});
}
Poll::Pending
})
.await?;
if let Some(output) = result {
return Ok(output);
}
}
}
pub(crate) fn notify_wall_clock_changed() {
WALL_CLOCK_CHANGE_GENERATION.fetch_add(1, Ordering::AcqRel);
unsafe { WALL_CLOCK_CHANGE_EVENT.wake(IoEvents::IN) };
}
struct ClockChangeWake(scheduler::thread::ThreadWakeHandle);
impl Wake for ClockChangeWake {
fn wake(self: Arc<Self>) {
self.0.wake();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.wake();
}
}
struct ClockChangeListener {
generation: u64,
registration: Option<Box<dyn PollRegistration>>,
}
impl ClockChangeListener {
fn new() -> Self {
Self {
generation: WALL_CLOCK_CHANGE_GENERATION.load(Ordering::Acquire),
registration: None,
}
}
}
impl Future for ClockChangeListener {
type Output = ();
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
if WALL_CLOCK_CHANGE_GENERATION.load(Ordering::Acquire) != self.generation
|| self
.registration
.as_ref()
.is_some_and(|entry| entry.was_notified())
{
return Poll::Ready(());
}
if self.registration.is_none() {
self.registration = unsafe {
WALL_CLOCK_CHANGE_EVENT.register(
context.waker(),
IoEvents::IN,
RegistrationMode::Shared,
)
};
}
if WALL_CLOCK_CHANGE_GENERATION.load(Ordering::Acquire) != self.generation
|| self
.registration
.as_ref()
.is_some_and(|entry| entry.was_notified())
{
Poll::Ready(())
} else {
Poll::Pending
}
}
}
pub(crate) struct WallClockWaiter {
listener: ClockChangeListener,
waker: Waker,
}
impl WallClockWaiter {
pub(crate) fn new(task: &UserTaskRef) -> Self {
let mut waiter = Self {
listener: ClockChangeListener::new(),
waker: Waker::from(Arc::new(ClockChangeWake(task.wake_handle()))),
};
waiter.refresh();
waiter
}
pub(crate) fn refresh(&mut self) -> bool {
let mut changed = false;
let mut context = Context::from_waker(&self.waker);
while Pin::new(&mut self.listener).poll(&mut context).is_ready() {
changed = true;
self.listener = ClockChangeListener::new();
}
changed
}
}
fn wall_deadline_to_monotonic(deadline: TimeValue) -> TimeValue {
ClockDeadline::Realtime(deadline).resolve_monotonic(ClockSnapshot::capture())
}