coven_storage/setup_waits.rs
1//! The waits one storage setup call spends on requests a provider refused
2//! for rate limiting (§20.8).
3//!
4//! Setup runs while the person waits for it, so a refused request is sent
5//! again in place instead of failing setup. Every step of one call draws on
6//! one minute; past it, the refusal fails setup.
7
8use crate::{cooldown::throttled_wait, StorageError, StorageFailure};
9use coven_foundation::clock::Clock;
10use coven_foundation::locks::lock_unpoisoned;
11use std::{future::Future, sync::Mutex, time::Duration};
12
13/// The longest one setup call waits, in total, to send rate-limited
14/// requests again.
15pub const SETUP_RATE_LIMIT_WAIT: Duration = Duration::from_secs(60);
16
17/// What one setup call has spent waiting to send refused requests again.
18/// The call's steps draw on it until it connects; then the connection the
19/// call makes beside the store's for its own requests
20/// ([`crate::StorageConnection::during_setup`]) takes it, and both go when
21/// the call and its requests end. The store's connection never resends.
22#[derive(Default)]
23pub struct SetupWaits(Mutex<Spent>);
24
25#[derive(Default)]
26struct Spent {
27 resends: u32,
28 waited: Duration,
29}
30
31impl SetupWaits {
32 /// Whether to send the request that failed with `error` again, after
33 /// waiting by `clock`: a rate-limited refusal whose cleanup did not also
34 /// fail, while the wait fits in what is left of
35 /// [`SETUP_RATE_LIMIT_WAIT`]. The wait is the provider's request or what
36 /// is left of the cooldown that refused it, and never less than the
37 /// ordinary delay (§6.7).
38 pub(crate) async fn wait_to_resend(&self, clock: &dyn Clock, error: &StorageError) -> bool {
39 if error.own_failure() != Some(StorageFailure::RateLimited) {
40 return false;
41 }
42 let (wait, resends) = {
43 let mut spent = lock_unpoisoned(&self.0);
44 let wait = throttled_wait(error, spent.resends);
45 let waited = spent.waited.saturating_add(wait);
46 if waited > SETUP_RATE_LIMIT_WAIT {
47 return false;
48 }
49 spent.waited = waited;
50 spent.resends += 1;
51 (wait, spent.resends)
52 };
53 tracing::info!(?wait, resends, "storage setup rate limited; sending again");
54 clock.wait_out(wait).await;
55 true
56 }
57
58 /// Send `request`, and again after each rate-limited refusal this call
59 /// can still wait out. Only for a request whose refusal stored nothing.
60 pub async fn resend<T, F: Future<Output = Result<T, StorageError>>>(
61 &self,
62 clock: &dyn Clock,
63 mut request: impl FnMut() -> F,
64 ) -> Result<T, StorageError> {
65 loop {
66 let result = request().await;
67 if let Err(error) = &result {
68 if self.wait_to_resend(clock, error).await {
69 continue;
70 }
71 }
72 return result;
73 }
74 }
75}
76
77#[cfg(test)]
78#[path = "setup_waits_tests.rs"]
79mod tests;