use std::collections::BTreeMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Condvar, Mutex, MutexGuard, OnceLock, PoisonError};
use std::task::{Context, Poll, Waker};
use std::time::Instant;
use crate::shared::router::DeadlineTimer;
use crate::shared::transport::BoxFuture;
struct Timers {
pending: Mutex<BTreeMap<(Instant, u64), Waker>>,
changed: Condvar,
next_id: AtomicU64,
running: bool,
}
impl Timers {
fn pending(&self) -> MutexGuard<'_, BTreeMap<(Instant, u64), Waker>> {
self.pending.lock().unwrap_or_else(PoisonError::into_inner)
}
}
fn timers() -> &'static Timers {
static TIMERS: OnceLock<Timers> = OnceLock::new();
TIMERS.get_or_init(|| {
let running = std::thread::Builder::new()
.name("connectrpc-deadlines".to_string())
.spawn(|| run(timers()))
.map_err(|e| {
tracing::error!(error = %e, "could not start the deadline timer thread; call deadlines are not enforced");
})
.is_ok();
Timers {
pending: Mutex::new(BTreeMap::new()),
changed: Condvar::new(),
next_id: AtomicU64::new(0),
running,
}
})
}
fn run(timers: &'static Timers) {
let mut pending = timers.pending();
loop {
let now = Instant::now();
let mut due = Vec::new();
while let Some(entry) = pending.first_entry() {
if entry.key().0 > now {
break;
}
due.push(entry.remove());
}
if !due.is_empty() {
drop(pending);
for waker in due {
waker.wake();
}
pending = timers.pending();
continue;
}
pending = match pending.keys().next().map(|(deadline, _)| *deadline) {
Some(deadline) => {
let wait = deadline.saturating_duration_since(now);
timers
.changed
.wait_timeout(pending, wait)
.unwrap_or_else(PoisonError::into_inner)
.0
}
None => timers
.changed
.wait(pending)
.unwrap_or_else(PoisonError::into_inner),
};
}
}
pub(crate) struct ThreadTimer;
impl DeadlineTimer for ThreadTimer {
fn sleep_until(&self, deadline: Instant) -> BoxFuture<'_, ()> {
let timers = timers();
if !timers.running {
return Box::pin(std::future::pending());
}
Box::pin(Sleep {
timers,
key: (deadline, timers.next_id.fetch_add(1, Ordering::Relaxed)),
registered: false,
})
}
}
struct Sleep {
timers: &'static Timers,
key: (Instant, u64),
registered: bool,
}
impl Future for Sleep {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.key.0 {
if self.registered {
self.timers.pending().remove(&self.key);
self.registered = false;
}
return Poll::Ready(());
}
let key = self.key;
let earliest = {
let mut pending = self.timers.pending();
pending.insert(key, cx.waker().clone());
pending.keys().next() == Some(&key)
};
self.registered = true;
if earliest {
self.timers.changed.notify_one();
}
Poll::Pending
}
}
impl Drop for Sleep {
fn drop(&mut self) {
if self.registered {
self.timers.pending().remove(&self.key);
}
}
}