qcs_api_client_common/
backoff.rs1use std::time::Duration;
7
8use http::StatusCode;
9
10use ::backoff::backoff::Backoff;
11pub use ::backoff::*;
12
13#[allow(clippy::module_name_repetitions)]
18#[must_use]
19pub fn default_backoff() -> ExponentialBackoff {
20 ExponentialBackoffBuilder::new()
21 .with_max_elapsed_time(Some(Duration::from_secs(300)))
22 .with_max_interval(Duration::from_secs(30))
23 .build()
24}
25
26#[must_use]
28pub const fn status_code_is_retry(code: StatusCode) -> bool {
29 matches!(
30 code,
31 StatusCode::SERVICE_UNAVAILABLE | StatusCode::TOO_MANY_REQUESTS
32 )
33}
34
35#[must_use]
38pub fn duration_from_response(
39 status: StatusCode,
40 headers: &http::HeaderMap,
41 backoff: &mut ExponentialBackoff,
42) -> Option<Duration> {
43 use time::{format_description::well_known::Rfc2822, OffsetDateTime};
44
45 if status_code_is_retry(status) {
46 if let Some(value) = headers.get(http::header::RETRY_AFTER) {
47 if let Ok(value) = value.to_str() {
48 if let Ok(value) = value.parse::<u64>() {
49 return Some(Duration::from_secs(value));
50 } else if let Ok(date) = OffsetDateTime::parse(value, &Rfc2822) {
51 let duration = date - OffsetDateTime::now_utc();
52 let millis = duration.whole_milliseconds().try_into().ok()?;
54 return Some(Duration::from_millis(millis));
55 }
56 }
57 }
58
59 backoff.next_backoff()
60 } else {
61 None
62 }
63}
64
65fn can_retry_method(method: &http::Method) -> bool {
66 method.is_safe()
74}
75
76#[must_use]
79pub fn duration_from_reqwest_error(
80 method: &http::Method,
81 error: &reqwest::Error,
82 backoff: &mut ExponentialBackoff,
83) -> Option<Duration> {
84 if can_retry_method(method) {
85 if error.is_timeout()
89 || error.is_connect()
90 || error.is_request()
91 || format!("{error:?}").contains("source: hyper::Error(ChannelClosed)")
92 {
93 backoff.next_backoff()
94 } else {
95 None
96 }
97 } else {
98 None
99 }
100}
101
102#[must_use]
105pub fn duration_from_io_error(
106 method: &http::Method,
107 error: &std::io::Error,
108 backoff: &mut ExponentialBackoff,
109) -> Option<Duration> {
110 use std::io::ErrorKind;
111 if can_retry_method(method) {
112 if matches!(
113 error.kind(),
114 ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted
115 ) {
116 backoff.next_backoff()
117 } else {
118 None
119 }
120 } else {
121 None
122 }
123}