use std::future::{poll_fn, Future};
use std::sync::{Condvar, Mutex, OnceLock};
use std::task::{Poll, Waker};
use std::time::{Duration, Instant};
struct Timer {
pending: Mutex<Vec<(Instant, Waker)>>,
signal: Condvar,
}
fn shared_timer() -> &'static Timer {
static SHARED: OnceLock<&'static Timer> = OnceLock::new();
SHARED.get_or_init(|| {
let shared: &'static Timer = Box::leak(Box::new(Timer {
pending: Mutex::new(Vec::new()),
signal: Condvar::new(),
}));
std::thread::Builder::new()
.name("nord-usb-deadline".into())
.spawn(move || run(shared))
.expect("spawning the deadline thread");
shared
})
}
fn run(t: &'static Timer) {
let mut pending = t.pending.lock().unwrap();
loop {
let now = Instant::now();
let mut i = 0;
while i < pending.len() {
if pending[i].0 <= now {
let (_, waker) = pending.swap_remove(i);
waker.wake();
} else {
i += 1;
}
}
let next = pending.iter().map(|(at, _)| *at).min();
pending = match next {
Some(at) => {
let wait = at.saturating_duration_since(Instant::now());
t.signal.wait_timeout(pending, wait).unwrap().0
}
None => t.signal.wait(pending).unwrap(),
};
}
}
fn register(at: Instant, waker: Waker) {
let t = shared_timer();
t.pending.lock().unwrap().push((at, waker));
t.signal.notify_one();
}
pub async fn with_timeout<F: Future>(fut: F, limit: Duration) -> Option<F::Output> {
let mut fut = Box::pin(fut);
let deadline = Instant::now() + limit;
let mut armed = false;
poll_fn(move |cx| {
if let Poll::Ready(v) = fut.as_mut().poll(cx) {
return Poll::Ready(Some(v));
}
if Instant::now() >= deadline {
return Poll::Ready(None);
}
if !armed {
armed = true;
register(deadline, cx.waker().clone());
}
Poll::Pending
})
.await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_ready_future_returns_its_value() {
let got = pollster::block_on(with_timeout(async { 7 }, Duration::from_secs(60)));
assert_eq!(got, Some(7));
}
#[test]
fn a_future_that_never_completes_times_out() {
let got = pollster::block_on(with_timeout(
poll_fn(|_| Poll::<()>::Pending),
Duration::from_millis(50),
));
assert_eq!(got, None);
}
#[test]
fn deadlines_fire_independently_of_registration_order() {
let slow = std::thread::spawn(|| {
pollster::block_on(with_timeout(
poll_fn(|_| Poll::<()>::Pending),
Duration::from_secs(30),
))
});
std::thread::sleep(Duration::from_millis(20));
let quick = pollster::block_on(with_timeout(
poll_fn(|_| Poll::<()>::Pending),
Duration::from_millis(50),
));
assert_eq!(quick, None, "a later, shorter deadline did not fire first");
drop(slow); }
}