diff --git a/src/cargo/util/network/retry.rs b/src/cargo/util/network/retry.rs index 5cb7d1e4fbd..742c2f01c78 100644 --- a/src/cargo/util/network/retry.rs +++ b/src/cargo/util/network/retry.rs @@ -1,4 +1,45 @@ //! Utilities for retrying a network operation. +//! +//! Some network errors are considered "spurious", meaning it is not a real +//! error (such as a 404 not found) and is likely a transient error (like a +//! bad network connection) that we can hope will resolve itself shortly. The +//! [`Retry`] type offers a way to repeatedly perform some kind of network +//! operation with a delay if it detects one of these possibly transient +//! errors. +//! +//! This supports errors from [`git2`], [`gix`], [`curl`], and +//! [`HttpNotSuccessful`] 5xx HTTP errors. +//! +//! The number of retries can be configured by the user via the `net.retry` +//! config option. This indicates the number of times to retry the operation +//! (default 3 times for a total of 4 attempts). +//! +//! There are hard-coded constants that indicate how long to sleep between +//! retries. The constants are tuned to balance a few factors, such as the +//! responsiveness to the user (we don't want cargo to hang for too long +//! retrying things), and accommodating things like Cloudfront's default +//! negative TTL of 10 seconds (if Cloudfront gets a 5xx error for whatever +//! reason it won't try to fetch again for 10 seconds). +//! +//! The timeout also implements a primitive form of random jitter. This is so +//! that if multiple requests fail at the same time that they don't all flood +//! the server at the same time when they are retried. This jitter still has +//! some clumping behavior, but should be good enough. +//! +//! [`Retry`] is the core type for implementing retry logic. The +//! [`Retry::try`] method can be called with a callback, and it will +//! indicate if it needs to be called again sometime in the future if there +//! was a possibly transient error. The caller is responsible for sleeping the +//! appropriate amount of time and then calling [`Retry::try`] again. +//! +//! [`with_retry`] is a convenience function that will create a [`Retry`] and +//! handle repeatedly running a callback until it succeeds, or it runs out of +//! retries. +//! +//! Some interesting resources about retries: +//! - +//! - +//! - use crate::util::errors::HttpNotSuccessful; use crate::{CargoResult, Config}; @@ -7,15 +48,32 @@ use rand::Rng; use std::cmp::min; use std::time::Duration; +/// State for managing retrying a network operation. pub struct Retry<'a> { config: &'a Config, + /// The number of failed attempts that have been done so far. + /// + /// Starts at 0, and increases by one each time an attempt fails. retries: u64, + /// The maximum number of times the operation should be retried. + /// + /// 0 means it should never retry. max_retries: u64, } +/// The result of attempting some operation via [`Retry::try`]. pub enum RetryResult { + /// The operation was successful. + /// + /// The wrapped value is the return value of the callback function. Success(T), + /// The operation was an error, and it should not be tried again. Err(anyhow::Error), + /// The operation failed, and should be tried again in the future. + /// + /// The wrapped value is the number of milliseconds to wait before trying + /// again. The caller is responsible for waiting this long and then + /// calling [`Retry::try`] again. Retry(u64), } @@ -40,7 +98,9 @@ impl<'a> Retry<'a> { }) } - /// Returns `Ok(None)` for operations that should be re-tried. + /// Calls the given callback, and returns a [`RetryResult`] which + /// indicates whether or not this needs to be called again at some point + /// in the future to retry the operation if it failed. pub fn r#try(&mut self, f: impl FnOnce() -> CargoResult) -> RetryResult { match f() { Err(ref e) if maybe_spurious(e) && self.retries < self.max_retries => { diff --git a/src/cargo/util/network/sleep.rs b/src/cargo/util/network/sleep.rs index fab53263b7d..d3c770f8563 100644 --- a/src/cargo/util/network/sleep.rs +++ b/src/cargo/util/network/sleep.rs @@ -68,7 +68,6 @@ impl SleepTracker { let now = Instant::now(); let mut result = Vec::new(); while let Some(next) = self.heap.peek() { - tracing::debug!("ERIC: now={now:?} next={:?}", next.wakeup); if next.wakeup < now { result.push(self.heap.pop().unwrap().data); } else {