use crate::platform::time::Instant;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
struct State {
pending: AtomicBool,
deadline: Mutex<Option<Instant>>,
hooks: Mutex<Option<Hooks>>,
}
static GLOBAL: State = State::new();
#[derive(Clone)]
struct Hooks {
signal: Arc<dyn Fn() + Send + Sync>,
schedule: Option<Arc<dyn Fn(Instant) + Send + Sync>>,
}
impl State {
const fn new() -> Self {
Self {
pending: AtomicBool::new(false),
deadline: Mutex::new(None),
hooks: Mutex::new(None),
}
}
fn hooks(&self) -> Option<Hooks> {
self.hooks.lock().ok().and_then(|h| h.clone())
}
fn wake(&self) {
self.pending.store(true, Ordering::Release);
if let Some(hooks) = self.hooks() {
(hooks.signal)();
}
}
fn set_deadline(&self, deadline: Option<Instant>) {
if let Ok(mut d) = self.deadline.lock() {
*d = deadline;
}
if let Some(at) = deadline
&& let Some(schedule) = self.hooks().and_then(|h| h.schedule)
{
schedule(at);
}
}
fn deadline(&self) -> Option<Instant> {
self.deadline.lock().ok().and_then(|d| *d)
}
fn take(&self, now: Instant) -> bool {
let woken = self.pending.swap(false, Ordering::AcqRel);
let due = self
.deadline
.lock()
.map(|mut d| match *d {
Some(at) if at <= now => {
*d = None;
true
}
_ => false,
})
.unwrap_or(false);
woken || due
}
fn timeout(&self, now: Instant) -> Option<std::time::Duration> {
if self.pending.load(Ordering::Acquire) {
return Some(std::time::Duration::ZERO);
}
self.deadline().map(|at| at.saturating_duration_since(now))
}
}
pub fn wake() {
GLOBAL.wake();
}
pub fn set_deadline(deadline: Option<Instant>) {
GLOBAL.set_deadline(deadline);
}
pub fn deadline() -> Option<Instant> {
GLOBAL.deadline()
}
pub fn take(now: Instant) -> bool {
let woken = GLOBAL.take(now);
let frame = super::frame::take_due(now);
woken || frame
}
pub fn timeout(now: Instant) -> Option<std::time::Duration> {
let frame = super::frame::deadline().map(|at| at.saturating_duration_since(now));
match (GLOBAL.timeout(now), frame) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
}
}
pub(crate) fn schedule(at: Instant) {
if let Some(schedule) = GLOBAL.hooks().and_then(|h| h.schedule) {
schedule(at);
}
}
pub(crate) fn install(
signal: Arc<dyn Fn() + Send + Sync>,
schedule: Option<Arc<dyn Fn(Instant) + Send + Sync>>,
) {
if let Ok(mut h) = GLOBAL.hooks.lock() {
*h = Some(Hooks { signal, schedule });
}
if GLOBAL.pending.load(Ordering::Acquire) {
wake();
}
}
#[cfg(target_os = "linux")]
#[allow(unsafe_code)]
mod unix {
use std::sync::OnceLock;
use std::time::Instant;
#[repr(C)]
struct PollFd {
fd: i32,
events: i16,
revents: i16,
}
const POLLIN: i16 = 1;
const O_NONBLOCK: i32 = 0o4000;
const O_CLOEXEC: i32 = 0o2_000_000;
const EINTR: i32 = 4;
unsafe extern "C" {
fn pipe2(fds: *mut i32, flags: i32) -> i32;
fn poll(fds: *mut PollFd, n: u64, timeout_ms: i32) -> i32;
fn read(fd: i32, buf: *mut std::ffi::c_void, n: usize) -> isize;
fn write(fd: i32, buf: *const std::ffi::c_void, n: usize) -> isize;
fn __errno_location() -> *mut i32;
}
fn pipe() -> Option<(i32, i32)> {
static PIPE: OnceLock<Option<(i32, i32)>> = OnceLock::new();
*PIPE.get_or_init(|| {
let mut fds = [0i32; 2];
(unsafe { pipe2(fds.as_mut_ptr(), O_NONBLOCK | O_CLOEXEC) } == 0)
.then_some((fds[0], fds[1]))
})
}
pub(crate) fn install() {
let Some((_, write_end)) = pipe() else {
return;
};
super::install(
std::sync::Arc::new(move || {
let _ = unsafe { write(write_end, [1u8].as_ptr().cast(), 1) };
}),
None,
);
}
pub(crate) fn wait_readable(fd: i32) -> bool {
let pipe = pipe();
loop {
let now = Instant::now();
let timeout = super::timeout(now);
let ms = match timeout {
Some(t) => t.as_micros().div_ceil(1000).min(i32::MAX as u128) as i32,
None => -1,
};
let mut fds = [
PollFd {
fd,
events: POLLIN,
revents: 0,
},
PollFd {
fd: pipe.map_or(-1, |p| p.0),
events: POLLIN,
revents: 0,
},
];
let n = unsafe { poll(fds.as_mut_ptr(), 2, ms) };
if n < 0 {
if unsafe { *__errno_location() } == EINTR {
continue;
}
return true; }
if fds[1].revents != 0
&& let Some((read_end, _)) = pipe
{
let mut buf = [0u8; 64];
while unsafe { read(read_end, buf.as_mut_ptr().cast(), buf.len()) } > 0 {}
}
if fds[0].revents != 0 {
return true;
}
if fds[1].revents != 0 || super::timeout(Instant::now()) == Some(Default::default()) {
return false;
}
if n == 0 {
return false; }
}
}
}
#[cfg(target_os = "linux")]
pub(crate) use unix::{install as install_unix, wait_readable};
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn wakes_and_deadlines() {
let state = State::new();
let now = Instant::now();
assert!(!state.take(now));
state.wake();
assert_eq!(state.timeout(now), Some(Duration::ZERO));
assert!(state.take(now));
assert!(!state.take(now), "a wake is delivered once");
state.set_deadline(Some(now + Duration::from_millis(50)));
assert!(state.timeout(now).is_some_and(|t| t > Duration::ZERO));
assert!(!state.take(now), "not due yet");
assert!(state.take(now + Duration::from_millis(60)));
assert_eq!(state.deadline(), None, "a due deadline is consumed");
}
}