use std::fmt;
use std::sync::Arc;
use crate::config::Timeouts;
use crate::transport::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Timeout {
Global,
PerCall,
Resolve,
Connect,
SendRequest,
#[doc(hidden)]
Await100,
SendBody,
RecvResponse,
RecvBody,
}
impl Timeout {
fn preceeding(&self) -> impl Iterator<Item = Timeout> {
let prev: &[Timeout] = match self {
Timeout::Resolve => &[Timeout::PerCall],
Timeout::Connect => &[Timeout::Resolve],
Timeout::SendRequest => &[Timeout::Connect],
Timeout::Await100 => &[Timeout::SendRequest],
Timeout::SendBody => &[Timeout::SendRequest, Timeout::Await100],
Timeout::RecvResponse => &[Timeout::SendRequest, Timeout::SendBody],
Timeout::RecvBody => &[Timeout::RecvResponse],
_ => &[],
};
prev.iter().copied()
}
fn timeouts_to_check(&self) -> impl Iterator<Item = Timeout> {
self.preceeding().chain([Timeout::Global, Timeout::PerCall])
}
fn configured_timeout(&self, timeouts: &Timeouts) -> Option<Duration> {
match self {
Timeout::Global => timeouts.global,
Timeout::PerCall => timeouts.per_call,
Timeout::Resolve => timeouts.resolve,
Timeout::Connect => timeouts.connect,
Timeout::SendRequest => timeouts.send_request,
Timeout::Await100 => timeouts.await_100,
Timeout::SendBody => timeouts.send_body,
Timeout::RecvResponse => timeouts.recv_response,
Timeout::RecvBody => timeouts.recv_body,
}
.map(Into::into)
}
}
#[derive(Default, Debug)]
pub(crate) struct CallTimings {
timeouts: Box<Timeouts>,
current_time: CurrentTime,
times: Vec<(Timeout, Instant)>,
}
impl CallTimings {
pub(crate) fn new(timeouts: Timeouts, current_time: CurrentTime) -> Self {
let mut times = Vec::with_capacity(8);
let now = current_time.now();
times.push((Timeout::Global, now));
times.push((Timeout::PerCall, now));
CallTimings {
timeouts: Box::new(timeouts),
current_time,
times,
}
}
pub(crate) fn new_call(mut self) -> CallTimings {
self.times.truncate(1); self.times.push((Timeout::PerCall, self.current_time.now()));
CallTimings {
timeouts: self.timeouts,
current_time: self.current_time,
times: self.times,
}
}
pub(crate) fn now(&self) -> Instant {
self.current_time.now()
}
pub(crate) fn record_time(&mut self, timeout: Timeout) {
assert!(
self.time_of(timeout).is_none(),
"{:?} recorded more than once",
timeout
);
let any_preceeding = timeout
.preceeding()
.filter_map(|to_check| self.time_of(to_check))
.any(|_| true);
assert!(any_preceeding, "{:?} has no preceeding", timeout);
self.times.push((timeout, self.current_time.now()));
}
fn time_of(&self, timeout: Timeout) -> Option<Instant> {
self.times.iter().find(|x| x.0 == timeout).map(|x| x.1)
}
pub(crate) fn next_timeout(&self, timeout: Timeout) -> NextTimeout {
let (reason, at) = timeout
.timeouts_to_check()
.filter_map(|to_check| {
let time = self.time_of(to_check)?;
let timeout = to_check.configured_timeout(&self.timeouts)?;
Some((to_check, time + timeout))
})
.min_by(|a, b| a.1.cmp(&b.1))
.unwrap_or((Timeout::Global, Instant::NotHappening));
let now = self.now();
let after = at.duration_since(now);
NextTimeout { after, reason }
}
}
#[derive(Clone)]
pub(crate) struct CurrentTime(Arc<dyn Fn() -> Instant + Send + Sync + 'static>);
impl CurrentTime {
pub(crate) fn now(&self) -> Instant {
self.0()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NextTimeout {
pub after: Duration,
pub reason: Timeout,
}
impl NextTimeout {
pub(crate) fn not_zero(&self) -> Option<Duration> {
if self.after.is_not_happening() {
None
} else if self.after.is_zero() {
Some(Duration::from_secs(1))
} else {
Some(self.after)
}
}
}
impl fmt::Debug for CurrentTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("CurrentTime").finish()
}
}
impl Default for CurrentTime {
fn default() -> Self {
Self(Arc::new(Instant::now))
}
}
impl fmt::Display for Timeout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let r = match self {
Timeout::Global => "global",
Timeout::PerCall => "per call",
Timeout::Resolve => "resolve",
Timeout::Connect => "connect",
Timeout::SendRequest => "send request",
Timeout::SendBody => "send body",
Timeout::Await100 => "await 100",
Timeout::RecvResponse => "receive response",
Timeout::RecvBody => "receive body",
};
write!(f, "{}", r)
}
}