[go: up one dir, main page]

ureq/
timings.rs

1use std::fmt;
2use std::iter::once;
3use std::sync::Arc;
4
5use crate::config::Timeouts;
6use crate::transport::time::{Duration, Instant};
7
8/// The various timeouts.
9///
10/// Each enum corresponds to a value in
11/// [`ConfigBuilder::timeout_xxx`][crate::config::ConfigBuilder::timeout_global].
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum Timeout {
15    /// Timeout for entire operation.
16    Global,
17
18    /// Timeout for the current call (when redirected).
19    PerCall,
20
21    /// Timeout in the resolver.
22    Resolve,
23
24    /// Timeout while opening the connection.
25    Connect,
26
27    /// Timeout while sending the request headers.
28    SendRequest,
29
30    /// Internal value never seen outside ureq (since awaiting 100 is expected
31    /// to timeout).
32    #[doc(hidden)]
33    Await100,
34
35    /// Timeout when sending then request body.
36    SendBody,
37
38    /// Timeout while receiving the response headers.
39    RecvResponse,
40
41    /// Timeout while receiving the response body.
42    RecvBody,
43}
44
45impl Timeout {
46    /// Give the immediate preceeding Timeout
47    fn preceeding(&self) -> impl Iterator<Item = Timeout> {
48        let prev: &[Timeout] = match self {
49            Timeout::Resolve => &[Timeout::PerCall],
50            Timeout::Connect => &[Timeout::Resolve],
51            Timeout::SendRequest => &[Timeout::Connect],
52            Timeout::Await100 => &[Timeout::SendRequest],
53            Timeout::SendBody => &[Timeout::SendRequest, Timeout::Await100],
54            Timeout::RecvResponse => &[Timeout::SendRequest, Timeout::SendBody],
55            Timeout::RecvBody => &[Timeout::RecvResponse],
56            _ => &[],
57        };
58
59        prev.iter().copied()
60    }
61
62    /// All timeouts to check
63    fn timeouts_to_check(&self) -> impl Iterator<Item = Timeout> {
64        // Always check Global and PerCall
65        once(*self)
66            .chain(self.preceeding())
67            .chain([Timeout::Global, Timeout::PerCall])
68    }
69
70    /// Get the corresponding configured timeout
71    fn configured_timeout(&self, timeouts: &Timeouts) -> Option<Duration> {
72        match self {
73            Timeout::Global => timeouts.global,
74            Timeout::PerCall => timeouts.per_call,
75            Timeout::Resolve => timeouts.resolve,
76            Timeout::Connect => timeouts.connect,
77            Timeout::SendRequest => timeouts.send_request,
78            Timeout::Await100 => timeouts.await_100,
79            Timeout::SendBody => timeouts.send_body,
80            Timeout::RecvResponse => timeouts.recv_response,
81            Timeout::RecvBody => timeouts.recv_body,
82        }
83        .map(Into::into)
84    }
85}
86
87#[derive(Default, Debug)]
88pub(crate) struct CallTimings {
89    timeouts: Box<Timeouts>,
90    current_time: CurrentTime,
91    times: Vec<(Timeout, Instant)>,
92}
93
94impl CallTimings {
95    pub(crate) fn new(timeouts: Timeouts, current_time: CurrentTime) -> Self {
96        let mut times = Vec::with_capacity(8);
97
98        let now = current_time.now();
99        times.push((Timeout::Global, now));
100        times.push((Timeout::PerCall, now));
101
102        CallTimings {
103            timeouts: Box::new(timeouts),
104            current_time,
105            times,
106        }
107    }
108
109    pub(crate) fn new_call(mut self) -> CallTimings {
110        self.times.truncate(1); // Global is in position 0.
111        self.times.push((Timeout::PerCall, self.current_time.now()));
112
113        CallTimings {
114            timeouts: self.timeouts,
115            current_time: self.current_time,
116            times: self.times,
117        }
118    }
119
120    pub(crate) fn now(&self) -> Instant {
121        self.current_time.now()
122    }
123
124    pub(crate) fn record_time(&mut self, timeout: Timeout) {
125        // Each time should only be recorded once
126        assert!(
127            self.time_of(timeout).is_none(),
128            "{:?} recorded more than once",
129            timeout
130        );
131
132        // There need to be at least one preceeding time recorded
133        // since it follows a graph/call tree.
134        let any_preceeding = timeout
135            .preceeding()
136            .filter_map(|to_check| self.time_of(to_check))
137            .any(|_| true);
138
139        assert!(any_preceeding, "{:?} has no preceeding", timeout);
140
141        // Record the time
142        self.times.push((timeout, self.current_time.now()));
143    }
144
145    fn time_of(&self, timeout: Timeout) -> Option<Instant> {
146        self.times.iter().find(|x| x.0 == timeout).map(|x| x.1)
147    }
148
149    pub(crate) fn next_timeout(&self, timeout: Timeout) -> NextTimeout {
150        let now = self.now();
151
152        let (reason, at) = timeout
153            .timeouts_to_check()
154            .filter_map(|to_check| {
155                let time = if to_check == timeout {
156                    now
157                } else {
158                    self.time_of(to_check)?
159                };
160                let timeout = to_check.configured_timeout(&self.timeouts)?;
161                Some((to_check, time + timeout))
162            })
163            .min_by(|a, b| a.1.cmp(&b.1))
164            .unwrap_or((Timeout::Global, Instant::NotHappening));
165
166        let after = at.duration_since(now);
167
168        NextTimeout { after, reason }
169    }
170}
171
172#[derive(Clone)]
173pub(crate) struct CurrentTime(Arc<dyn Fn() -> Instant + Send + Sync + 'static>);
174
175impl CurrentTime {
176    pub(crate) fn now(&self) -> Instant {
177        self.0()
178    }
179}
180
181/// A pair of [`Duration`] and [`Timeout`].
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub struct NextTimeout {
184    /// Duration until next timeout.
185    pub after: Duration,
186    /// The name of the next timeout.s
187    pub reason: Timeout,
188}
189
190impl NextTimeout {
191    /// Returns the duration of the timeout if the timeout must happen, but avoid instant timeouts
192    ///
193    /// If the timeout must happen but is zero, returns 1 second
194    pub fn not_zero(&self) -> Option<Duration> {
195        if self.after.is_not_happening() {
196            None
197        } else if self.after.is_zero() {
198            Some(Duration::from_secs(1))
199        } else {
200            Some(self.after)
201        }
202    }
203}
204
205impl fmt::Debug for CurrentTime {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        f.debug_tuple("CurrentTime").finish()
208    }
209}
210
211impl Default for CurrentTime {
212    fn default() -> Self {
213        Self(Arc::new(Instant::now))
214    }
215}
216
217impl fmt::Display for Timeout {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        let r = match self {
220            Timeout::Global => "global",
221            Timeout::PerCall => "per call",
222            Timeout::Resolve => "resolve",
223            Timeout::Connect => "connect",
224            Timeout::SendRequest => "send request",
225            Timeout::SendBody => "send body",
226            Timeout::Await100 => "await 100",
227            Timeout::RecvResponse => "receive response",
228            Timeout::RecvBody => "receive body",
229        };
230        write!(f, "{}", r)
231    }
232}