use std::{
cell::Cell,
ptr,
time::{Duration, Instant},
};
use cares_sys::ares_channel_t;
pub const DEADLINE_TOLERANCE: Duration = Duration::from_millis(1);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerDeadline {
pub at: Instant,
pub generation: u64,
}
pub struct TimerState {
deadline: Cell<Option<TimerDeadline>>,
generation: Cell<u64>,
expired: Cell<bool>,
}
impl TimerState {
#[must_use]
pub const fn new() -> Self {
Self {
deadline: Cell::new(None),
generation: Cell::new(0),
expired: Cell::new(false),
}
}
#[must_use]
pub fn refresh(&self, channel: *mut ares_channel_t) -> Option<TimerDeadline> {
if channel.is_null() {
return self.refresh_from_deadline(None);
}
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let timeout =
unsafe { cares_sys::ares_timeout(channel.cast_const(), ptr::null_mut(), &mut tv) };
if timeout.is_null() {
self.refresh_from_deadline(None)
} else {
self.refresh_from_deadline(Some(relative_to_instant(&tv)))
}
}
#[must_use]
pub fn on_timer(&self, generation: u64) -> bool {
match self.deadline.get() {
Some(deadline) if deadline.generation == generation => {
self.deadline.set(None);
self.expired.set(true);
true
}
_ => false,
}
}
#[must_use]
pub fn take_expired(&self) -> bool {
self.expired.replace(false)
}
pub fn disarm(&self) {
self.deadline.set(None);
self.bump_generation();
self.expired.set(false);
}
#[must_use]
pub fn deadline(&self) -> Option<TimerDeadline> {
self.deadline.get()
}
#[must_use]
pub fn refresh_from_deadline(&self, deadline: Option<Instant>) -> Option<TimerDeadline> {
let current = self.deadline.get();
let next = match (current, deadline) {
(None, None) => None,
(None, Some(at)) => Some(self.arm(at)),
(Some(_), None) => {
self.bump_generation();
None
}
(Some(armed), Some(at)) => {
if exceeds_tolerance(armed.at, at) {
Some(self.arm(at))
} else {
Some(armed)
}
}
};
self.deadline.set(next);
next
}
fn bump_generation(&self) -> u64 {
let generation = self.generation.get().wrapping_add(1);
self.generation.set(generation);
generation
}
fn arm(&self, at: Instant) -> TimerDeadline {
TimerDeadline {
at,
generation: self.bump_generation(),
}
}
}
impl Default for TimerState {
fn default() -> Self {
Self::new()
}
}
fn exceeds_tolerance(armed: Instant, candidate: Instant) -> bool {
let delta = if candidate >= armed {
candidate.duration_since(armed)
} else {
armed.duration_since(candidate)
};
delta > DEADLINE_TOLERANCE
}
fn relative_to_instant(tv: &libc::timeval) -> Instant {
let seconds = u64::try_from(tv.tv_sec).unwrap_or_default();
let microseconds = u64::try_from(tv.tv_usec).unwrap_or_default();
let relative = Duration::from_secs(seconds).saturating_add(Duration::from_micros(microseconds));
Instant::now()
.checked_add(relative)
.unwrap_or_else(Instant::now)
}