1use futures::Future;
2use rand::prelude::*;
3use std::{cell::RefCell, thread, time::Duration};
4
5thread_local!(static RNG: RefCell<rand::prelude::ThreadRng> = {
6 RefCell::new(rand::thread_rng())
7});
8
9#[derive(Debug)]
10pub enum Details {
11 Duplicate,
12 Throttled,
13 NotFound,
14 Unspecified,
15}
16
17#[derive(Debug)]
18pub enum Error<K> {
19 Transient(Details),
20 Permanent(Details),
21 Exhausted(Details),
22 CustomTransient(K),
23 CustomPermanent(K),
24 CustomExhausted(K),
25}
26
27impl<K> From<azure_core::error::Error> for Error<K> {
28 fn from(error: azure_core::error::Error) -> Self {
29 match error.kind() {
30 azure_core::error::ErrorKind::HttpResponse {
31 status,
32 error_code: _error_code,
33 } => match status {
34 404 => Error::Permanent(Details::NotFound),
35 409 => Error::Permanent(Details::Duplicate),
36 429 => Error::Transient(Details::Throttled),
37 _ => Error::Permanent(Details::Unspecified),
38 },
39 azure_core::error::ErrorKind::Io => Error::Transient(Details::Unspecified),
40 azure_core::error::ErrorKind::DataConversion => Error::Permanent(Details::Unspecified),
41 azure_core::error::ErrorKind::Credential => Error::Permanent(Details::Unspecified),
42 azure_core::error::ErrorKind::Other => Error::Transient(Details::Unspecified),
43 }
44 }
45}
46
47pub struct Settings<'a> {
48 pub attempts: u8,
49
50 pub initial_delay: Duration,
51
52 pub backoff: f64,
53
54 pub rng: Option<&'a mut rand::prelude::ThreadRng>,
56}
57
58pub async fn retry<'a, F, K, E, Fut>(
59 func: F,
60 settings: Option<&mut Settings<'a>>,
61) -> Result<K, Error<E>>
62where
63 F: Fn() -> Fut,
64 Fut: Future<Output = Result<K, Error<E>>>,
65{
66 let mut settings_holder;
67 let settings = match settings {
68 Some(settings) => settings,
69 None => {
70 settings_holder = Settings {
71 attempts: 5,
72 initial_delay: Duration::from_millis(100),
73 backoff: 2.0,
74 rng: None,
75 };
76 &mut settings_holder
77 }
78 };
79
80 let mut attempt = 1;
82
83 let mut wait = Duration::default();
84 loop {
85 match func().await {
86 Ok(k) => return Ok(k),
87 Err(err) => {
88 if attempt == settings.attempts {
89 let err = match err {
90 Error::Transient(err) => Error::Exhausted(err),
91 Error::Permanent(err) | Error::Exhausted(err) => Error::Exhausted(err),
92 Error::CustomPermanent(err)
93 | Error::CustomExhausted(err)
94 | Error::CustomTransient(err) => Error::CustomExhausted(err),
95 };
96
97 return Err(err);
98 }
99
100 match err {
101 Error::Transient(_) | Error::CustomTransient(_) => {
102 wait = if attempt == 1 {
103 settings.initial_delay
104 } else {
105 wait.mul_f64(settings.backoff)
106 };
107
108 let fuzz: f64 = match &mut settings.rng {
111 Some(rng) => rng.gen(),
112 None => RNG.with(|rng| rng.borrow_mut().gen::<f64>()),
113 };
114
115 let fuzz = fuzz / 2.0 + 0.75;
117
118 let wait = wait.mul_f64(fuzz);
120
121 thread::sleep(wait);
122
123 attempt += 1;
124 continue;
125 }
126 Error::Permanent(_)
127 | Error::Exhausted(_)
128 | Error::CustomPermanent(_)
129 | Error::CustomExhausted(_) => return Err(err),
130 }
131 }
132 };
133 }
134}