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::BAD_GATEWAY | 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 std_duration: Duration = duration.try_into().ok()?;
55 return Some(std_duration);
56 }
57 }
58 }
59
60 backoff.next_backoff()
61 } else {
62 None
63 }
64}
65
66fn can_retry_method(method: &http::Method) -> bool {
67 method.is_safe()
75}
76
77#[must_use]
80pub fn duration_from_reqwest_error(
81 method: &http::Method,
82 error: &reqwest::Error,
83 backoff: &mut ExponentialBackoff,
84) -> Option<Duration> {
85 if can_retry_method(method) {
86 if error.is_timeout()
90 || error.is_connect()
91 || error.is_request()
92 || format!("{error:?}").contains("source: hyper::Error(ChannelClosed)")
93 {
94 backoff.next_backoff()
95 } else {
96 None
97 }
98 } else {
99 None
100 }
101}
102
103#[must_use]
106pub fn duration_from_io_error(
107 method: &http::Method,
108 error: &std::io::Error,
109 backoff: &mut ExponentialBackoff,
110) -> Option<Duration> {
111 use std::io::ErrorKind;
112 if can_retry_method(method) {
113 if matches!(
114 error.kind(),
115 ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted
116 ) {
117 backoff.next_backoff()
118 } else {
119 None
120 }
121 } else {
122 None
123 }
124}