1
1
//! Utilities for retrying a network operation.
2
+ //!
3
+ //! Some network errors are considered "spurious", meaning it is not a real
4
+ //! error (such as a 404 not found) and is likely a transient error (like a
5
+ //! bad network connection) that we can hope will resolve itself shortly. The
6
+ //! [`Retry`] type offers a way to repeatedly perform some kind of network
7
+ //! operation with a delay if it detects one of these possibly transient
8
+ //! errors.
9
+ //!
10
+ //! This supports errors from [`git2`], [`gix`], [`curl`], and
11
+ //! [`HttpNotSuccessful`] 5xx HTTP errors.
12
+ //!
13
+ //! The number of retries can be configured by the user via the `net.retry`
14
+ //! config option. This indicates the number of times to retry the operation
15
+ //! (default 3 times for a total of 4 attempts).
16
+ //!
17
+ //! There are hard-coded constants that indicate how long to sleep between
18
+ //! retries. The constants are tuned to balance a few factors, such as the
19
+ //! responsiveness to the user (we don't want cargo to hang for too long
20
+ //! retrying things), and accommodating things like Cloudfront's default
21
+ //! negative TTL of 10 seconds (if Cloudfront gets a 5xx error for whatever
22
+ //! reason it won't try to fetch again for 10 seconds).
23
+ //!
24
+ //! The timeout also implements a primitive form of random jitter. This is so
25
+ //! that if multiple requests fail at the same time that they don't all flood
26
+ //! the server at the same time when they are retried. This jitter still has
27
+ //! some clumping behavior, but should be good enough.
28
+ //!
29
+ //! [`Retry`] is the core type for implementing retry logic. The
30
+ //! [`Retry::try`] method can be called with a callback, and it will
31
+ //! indicate if it needs to be called again sometime in the future if there
32
+ //! was a possibly transient error. The caller is responsible for sleeping the
33
+ //! appropriate amount of time and then calling [`Retry::try`] again.
34
+ //!
35
+ //! [`with_retry`] is a convenience function that will create a [`Retry`] and
36
+ //! handle repeatedly running a callback until it succeeds, or it runs out of
37
+ //! retries.
38
+ //!
39
+ //! Some interesting resources about retries:
40
+ //! - <https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/>
41
+ //! - <https://en.wikipedia.org/wiki/Exponential_backoff>
42
+ //! - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After>
2
43
3
44
use crate :: util:: errors:: HttpNotSuccessful ;
4
45
use crate :: { CargoResult , Config } ;
@@ -7,15 +48,32 @@ use rand::Rng;
7
48
use std:: cmp:: min;
8
49
use std:: time:: Duration ;
9
50
51
+ /// State for managing retrying a network operation.
10
52
pub struct Retry < ' a > {
11
53
config : & ' a Config ,
54
+ /// The number of failed attempts that have been done so far.
55
+ ///
56
+ /// Starts at 0, and increases by one each time an attempt fails.
12
57
retries : u64 ,
58
+ /// The maximum number of times the operation should be retried.
59
+ ///
60
+ /// 0 means it should never retry.
13
61
max_retries : u64 ,
14
62
}
15
63
64
+ /// The result of attempting some operation via [`Retry::try`].
16
65
pub enum RetryResult < T > {
66
+ /// The operation was successful.
67
+ ///
68
+ /// The wrapped value is the return value of the callback function.
17
69
Success ( T ) ,
70
+ /// The operation was an error, and it should not be tried again.
18
71
Err ( anyhow:: Error ) ,
72
+ /// The operation failed, and should be tried again in the future.
73
+ ///
74
+ /// The wrapped value is the number of milliseconds to wait before trying
75
+ /// again. The caller is responsible for waiting this long and then
76
+ /// calling [`Retry::try`] again.
19
77
Retry ( u64 ) ,
20
78
}
21
79
@@ -40,7 +98,9 @@ impl<'a> Retry<'a> {
40
98
} )
41
99
}
42
100
43
- /// Returns `Ok(None)` for operations that should be re-tried.
101
+ /// Calls the given callback, and returns a [`RetryResult`] which
102
+ /// indicates whether or not this needs to be called again at some point
103
+ /// in the future to retry the operation if it failed.
44
104
pub fn r#try < T > ( & mut self , f : impl FnOnce ( ) -> CargoResult < T > ) -> RetryResult < T > {
45
105
match f ( ) {
46
106
Err ( ref e) if maybe_spurious ( e) && self . retries < self . max_retries => {
0 commit comments