coven_storage/
cooldown.rs1use crate::{StorageError, StorageFailure};
9use std::time::Duration;
10
11pub const LONGEST_RETRY_DELAY: Duration = Duration::from_secs(300);
13
14pub fn retry_delay(attempt: u32) -> Duration {
17 let seconds = 1u64.checked_shl(attempt).unwrap_or(u64::MAX).min(LONGEST_RETRY_DELAY.as_secs());
18 Duration::from_secs(seconds)
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum RetryAfter {
24 Wait(Duration),
27 NoTimeBasis,
29}
30
31impl RetryAfter {
32 pub fn wait(self) -> Duration {
36 match self {
37 Self::Wait(wait) => wait,
38 Self::NoTimeBasis => LONGEST_RETRY_DELAY,
39 }
40 }
41}
42
43pub(crate) fn throttled_wait(error: &StorageError, attempt: u32) -> Duration {
48 let requested = error.retry_after().map(RetryAfter::wait).or_else(|| match error {
49 StorageError::Caused { source, .. } => {
50 source.downcast_ref::<CoolingDown>().map(|cooling| cooling.remaining)
51 }
52 _ => None,
53 });
54 requested.unwrap_or_default().max(retry_delay(attempt))
55}
56
57#[async_trait::async_trait]
60pub trait Cooldown: Send + Sync {
61 fn remaining(&self) -> Option<Duration>;
63 async fn record(&self, wait: Duration) -> Result<(), StorageError>;
66}
67
68pub(crate) fn cooling(cooldown: &dyn Cooldown) -> Result<(), StorageError> {
70 match cooldown.remaining() {
71 Some(remaining) => Err(StorageFailure::RateLimited.with_source(CoolingDown { remaining })),
72 None => Ok(()),
73 }
74}
75
76pub(crate) async fn observed<T>(
78 cooldown: &dyn Cooldown,
79 result: Result<T, StorageError>,
80) -> Result<T, StorageError> {
81 let error = match result {
82 Ok(value) => return Ok(value),
83 Err(error) => error,
84 };
85 let Some(after) = error.retry_after() else {
86 return Err(error);
87 };
88 if after == RetryAfter::NoTimeBasis {
89 tracing::warn!(
90 ?error,
91 "Retry-After names a date without a response Date; waiting the longest ordinary delay"
92 );
93 }
94 let wait = after.wait();
95 match cooldown.record(wait).await {
96 Ok(()) => Err(error),
97 Err(cause) => Err(StorageFailure::File.with_source(CooldownNotRecorded {
98 wait,
99 response: error,
100 cause,
101 })),
102 }
103}
104
105pub struct NoCooldown;
107
108#[async_trait::async_trait]
109impl Cooldown for NoCooldown {
110 fn remaining(&self) -> Option<Duration> {
111 None
112 }
113 async fn record(&self, _: Duration) -> Result<(), StorageError> {
114 Ok(())
115 }
116}
117
118#[derive(Debug, thiserror::Error)]
120#[error("provider cooldown has {remaining:?} left")]
121pub struct CoolingDown {
122 pub remaining: Duration,
124}
125
126#[derive(Debug, thiserror::Error)]
129#[error("provider cooldown of {wait:?} was not persisted: {cause}")]
130pub struct CooldownNotRecorded {
131 pub wait: Duration,
133 #[source]
135 pub response: StorageError,
136 pub cause: StorageError,
138}
139
140#[cfg(test)]
141#[path = "cooldown_tests.rs"]
142pub(crate) mod tests;