Calculate the retry delay in milliseconds. Priority: 1. `retry-after-ms` header (milliseconds) 2. `retry-after` header (seconds as float, or HTTP-date) 3. Exponential backoff: `initial_delay * backoff_factor^(attempt-1)` When headers are present the delay is uncapped (up to `RETRY_MAX_DELAY`). Without headers the delay is capped at `RETRY_MAX_DELAY_NO_HEADERS`.
(attempt: u32, response_headers: Option<&HashMap<String, String>>)
| 83 | /// When headers are present the delay is uncapped (up to `RETRY_MAX_DELAY`). |
| 84 | /// Without headers the delay is capped at `RETRY_MAX_DELAY_NO_HEADERS`. |
| 85 | pub fn delay(attempt: u32, response_headers: Option<&HashMap<String, String>>) -> u64 { |
| 86 | if let Some(headers) = response_headers { |
| 87 | // 1. retry-after-ms |
| 88 | if let Some(val) = headers.get("retry-after-ms") { |
| 89 | if let Ok(ms) = val.parse::<f64>() { |
| 90 | if !ms.is_nan() { |
| 91 | return ms as u64; |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | // 2. retry-after (seconds or HTTP-date) |
| 97 | if let Some(val) = headers.get("retry-after") { |
| 98 | // Try as seconds first |
| 99 | if let Ok(secs) = val.parse::<f64>() { |
| 100 | if !secs.is_nan() { |
| 101 | return (secs * 1000.0).ceil() as u64; |
| 102 | } |
| 103 | } |
| 104 | // Try as HTTP-date via chrono |
| 105 | if let Ok(date) = chrono::DateTime::parse_from_rfc2822(val) { |
| 106 | let now = chrono::Utc::now(); |
| 107 | let diff_ms = (date.signed_duration_since(now)).num_milliseconds(); |
| 108 | if diff_ms > 0 { |
| 109 | return diff_ms as u64; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | // Headers present but no usable retry-after – uncapped backoff |
| 115 | return exponential_backoff( |
| 116 | attempt, |
| 117 | RETRY_INITIAL_DELAY, |
| 118 | RETRY_BACKOFF_FACTOR, |
| 119 | RETRY_MAX_DELAY, |
| 120 | ); |
| 121 | } |
| 122 | |
| 123 | // No headers at all – capped backoff |
| 124 | exponential_backoff( |
| 125 | attempt, |
| 126 | RETRY_INITIAL_DELAY, |
| 127 | RETRY_BACKOFF_FACTOR, |
| 128 | RETRY_MAX_DELAY_NO_HEADERS, |
| 129 | ) |
| 130 | } |
| 131 | |
| 132 | fn exponential_backoff(attempt: u32, initial: u64, factor: u64, max: u64) -> u64 { |
| 133 | let exp = factor.saturating_pow(attempt.saturating_sub(1)); |
no test coverage detected