restate_sdk_shared_core/
retries.rs1use crate::EntryRetryInfo;
2use std::cmp;
3use std::time::Duration;
4
5#[derive(Debug, Clone, Default)]
7pub enum RetryPolicy {
8 #[default]
12 Infinite,
13 None,
17 FixedDelay {
21 interval: Duration,
25
26 max_attempts: Option<u32>,
32
33 max_duration: Option<Duration>,
39 },
40 Exponential {
44 initial_interval: Duration,
48
49 factor: f32,
53
54 max_interval: Option<Duration>,
58
59 max_attempts: Option<u32>,
65
66 max_duration: Option<Duration>,
72 },
73}
74
75#[derive(Debug, Clone, Eq, PartialEq)]
76pub(crate) enum NextRetry {
77 Retry(Option<Duration>),
78 DoNotRetry,
79}
80
81impl RetryPolicy {
82 pub fn fixed_delay(
83 interval: Duration,
84 max_attempts: Option<u32>,
85 max_duration: Option<Duration>,
86 ) -> Self {
87 Self::FixedDelay {
88 interval,
89 max_attempts,
90 max_duration,
91 }
92 }
93
94 pub fn exponential(
95 initial_interval: Duration,
96 factor: f32,
97 max_attempts: Option<u32>,
98 max_interval: Option<Duration>,
99 max_duration: Option<Duration>,
100 ) -> Self {
101 Self::Exponential {
102 initial_interval,
103 factor,
104 max_attempts,
105 max_interval,
106 max_duration,
107 }
108 }
109
110 pub(crate) fn next_retry(&self, retry_info: EntryRetryInfo) -> NextRetry {
111 match self {
112 RetryPolicy::Infinite => NextRetry::Retry(None),
113 RetryPolicy::None => NextRetry::DoNotRetry,
114 RetryPolicy::FixedDelay {
115 interval,
116 max_attempts,
117 max_duration,
118 } => {
119 if max_attempts.is_some_and(|max_attempts| max_attempts <= retry_info.retry_count)
120 || max_duration
121 .is_some_and(|max_duration| max_duration <= retry_info.retry_loop_duration)
122 {
123 return NextRetry::DoNotRetry;
125 }
126
127 NextRetry::Retry(Some(*interval))
129 }
130 RetryPolicy::Exponential {
131 initial_interval,
132 factor,
133 max_interval,
134 max_attempts,
135 max_duration,
136 } => {
137 if max_attempts.is_some_and(|max_attempts| max_attempts <= retry_info.retry_count)
138 || max_duration
139 .is_some_and(|max_duration| max_duration <= retry_info.retry_loop_duration)
140 {
141 return NextRetry::DoNotRetry;
143 }
144
145 NextRetry::Retry(Some(cmp::min(
146 max_interval.unwrap_or(Duration::MAX),
147 initial_interval.mul_f32(factor.powi((retry_info.retry_count - 1) as i32)),
148 )))
149 }
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_exponential_policy() {
160 let policy = RetryPolicy::Exponential {
161 initial_interval: Duration::from_millis(100),
162 factor: 2.0,
163 max_interval: Some(Duration::from_millis(500)),
164 max_attempts: None,
165 max_duration: Some(Duration::from_secs(10)),
166 };
167
168 assert_eq!(
169 policy.next_retry(EntryRetryInfo {
170 retry_count: 2,
171 retry_loop_duration: Duration::from_secs(1)
172 }),
173 NextRetry::Retry(Some(Duration::from_millis(100).mul_f32(2.0)))
174 );
175 assert_eq!(
176 policy.next_retry(EntryRetryInfo {
177 retry_count: 3,
178 retry_loop_duration: Duration::from_secs(1)
179 }),
180 NextRetry::Retry(Some(Duration::from_millis(100).mul_f32(4.0)))
181 );
182 assert_eq!(
183 policy.next_retry(EntryRetryInfo {
184 retry_count: 4,
185 retry_loop_duration: Duration::from_secs(1)
186 }),
187 NextRetry::Retry(Some(Duration::from_millis(500)))
188 );
189 assert_eq!(
190 policy.next_retry(EntryRetryInfo {
191 retry_count: 4,
192 retry_loop_duration: Duration::from_secs(10)
193 }),
194 NextRetry::DoNotRetry
195 );
196 }
197}