[go: up one dir, main page]

ureq/
timings.rs

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