1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use super::{Backoff, BackoffOptions, GotBackoffDuration};
use qiniu_http::RequestParts as HttpRequestParts;
use rand::{thread_rng, Rng};
use std::{convert::TryInto, fmt::Debug, time::Duration, u64};
pub use num_rational::Ratio;
#[derive(Debug, Clone)]
pub struct RandomizedBackoff<P: ?Sized> {
minification: Ratio<u8>,
magnification: Ratio<u8>,
base_backoff: P,
}
impl<P> RandomizedBackoff<P> {
#[inline]
pub const fn new(base_backoff: P, minification: Ratio<u8>, magnification: Ratio<u8>) -> Self {
assert!(*minification.denom() > 0);
assert!(*magnification.denom() > 0);
Self {
base_backoff,
minification,
magnification,
}
}
#[inline]
pub const fn base_backoff(&self) -> &P {
&self.base_backoff
}
#[inline]
pub const fn minification(&self) -> Ratio<u8> {
self.minification
}
#[inline]
pub const fn magnification(&self) -> Ratio<u8> {
self.magnification
}
}
impl<P: Backoff + Clone> Backoff for RandomizedBackoff<P> {
fn time(&self, request: &mut HttpRequestParts, opts: BackoffOptions) -> GotBackoffDuration {
let duration = self.base_backoff().time(request, opts).duration();
let minification: Ratio<u128> = Ratio::new_raw(
self.minification().numer().to_owned().into(),
self.minification().denom().to_owned().into(),
);
let magnification: Ratio<u128> = Ratio::new_raw(
self.magnification().numer().to_owned().into(),
self.magnification().denom().to_owned().into(),
);
let minified: u64 = (minification * duration.as_nanos())
.to_integer()
.try_into()
.unwrap_or(u64::MAX);
let magnified: u64 = (magnification * duration.as_nanos())
.to_integer()
.try_into()
.unwrap_or(u64::MAX);
let randomized = thread_rng().gen_range(minified, magnified);
Duration::from_nanos(randomized).into()
}
}
impl<P: Default> Default for RandomizedBackoff<P> {
#[inline]
fn default() -> Self {
RandomizedBackoff::new(P::default(), Ratio::new_raw(1, 2), Ratio::new_raw(3, 2))
}
}
#[cfg(test)]
mod tests {
use super::{
super::{FixedBackoff, ResponseError, RetriedStatsInfo, RetryDecision},
*,
};
use qiniu_http::ResponseErrorKind as HttpResponseErrorKind;
use std::{error::Error, result::Result};
#[test]
fn test_randomized_backoff() -> Result<(), Box<dyn Error>> {
let fixed = FixedBackoff::new(Duration::from_secs(1));
let randomized = RandomizedBackoff::new(fixed, Ratio::new_raw(1, 2), Ratio::new_raw(3, 2));
for _ in 0..10000 {
let delay = randomized
.time(
&mut HttpRequestParts::default(),
BackoffOptions::builder(
&ResponseError::new_with_msg(HttpResponseErrorKind::TimeoutError.into(), "Test Error"),
&RetriedStatsInfo::default(),
)
.retry_decision(RetryDecision::RetryRequest)
.build(),
)
.duration();
assert!(delay >= Duration::from_millis(500));
assert!(delay != Duration::from_millis(1000));
assert!(delay < Duration::from_millis(1500));
}
Ok(())
}
}