google_cloud_storage/
retry_policy.rs1use google_cloud_gax::error::Error;
40use google_cloud_gax::{
41 retry_policy::{RetryPolicy, RetryPolicyExt},
42 retry_result::RetryResult,
43 retry_state::RetryState,
44};
45use std::sync::Arc;
46use std::time::Duration;
47
48pub(crate) fn storage_default() -> impl RetryPolicy {
53 RetryableErrors.with_time_limit(Duration::from_secs(300))
54}
55
56#[derive(Clone, Debug)]
76pub struct RetryableErrors;
77
78impl RetryPolicy for RetryableErrors {
79 fn on_error(&self, state: &RetryState, error: Error) -> RetryResult {
80 if error.is_transient_and_before_rpc() {
81 return RetryResult::Continue(error);
82 }
83 if !state.idempotent {
84 return RetryResult::Permanent(error);
85 }
86 if error.is_io() || error.is_timeout() {
87 return RetryResult::Continue(error);
88 }
89 if error.is_transport() && error.http_status_code().is_none() {
90 return RetryResult::Continue(error);
94 }
95 if let Some(code) = error.http_status_code() {
96 return match code {
97 408 | 429 | 500..600 => RetryResult::Continue(error),
98 _ => RetryResult::Permanent(error),
99 };
100 }
101 if let Some(code) = error.status().map(|s| s.code) {
102 use google_cloud_gax::error::rpc::Code;
103 return match code {
104 Code::Internal | Code::ResourceExhausted | Code::Unavailable => {
105 RetryResult::Continue(error)
106 }
107 Code::DeadlineExceeded => RetryResult::Continue(error),
110 _ => RetryResult::Permanent(error),
111 };
112 }
113 RetryResult::Permanent(error)
114 }
115}
116
117#[derive(Clone, Debug)]
121pub(crate) struct ContinueOn308<T> {
122 inner: T,
123}
124
125impl<T> ContinueOn308<T> {
126 pub fn new(inner: T) -> Self {
127 Self { inner }
128 }
129}
130
131impl RetryPolicy for ContinueOn308<Arc<dyn RetryPolicy + 'static>> {
132 fn on_error(&self, state: &RetryState, error: Error) -> RetryResult {
133 if error.http_status_code() == Some(308) {
134 return RetryResult::Continue(error);
135 }
136 self.inner.on_error(state, error)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use gaxi::grpc::tonic::Status;
144 use google_cloud_gax::error::rpc::Code;
145 use google_cloud_gax::throttle_result::ThrottleResult;
146 use http::HeaderMap;
147 use test_case::test_case;
148
149 #[test_case(408)]
150 #[test_case(429)]
151 #[test_case(500)]
152 #[test_case(502)]
153 #[test_case(503)]
154 #[test_case(504)]
155 fn retryable_http(code: u16) {
156 let p = RetryableErrors;
157 assert!(
158 p.on_error(&RetryState::new(true), http_error(code))
159 .is_continue()
160 );
161 assert!(
162 p.on_error(&RetryState::new(false), http_error(code))
163 .is_permanent()
164 );
165
166 let t = p.on_throttle(&RetryState::new(true), http_error(code));
167 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
168 }
169
170 #[test_case(401)]
171 #[test_case(403)]
172 fn not_recommended_http(code: u16) {
173 let p = RetryableErrors;
174 assert!(
175 p.on_error(&RetryState::new(true), http_error(code))
176 .is_permanent()
177 );
178 assert!(
179 p.on_error(&RetryState::new(false), http_error(code))
180 .is_permanent()
181 );
182
183 let t = p.on_throttle(&RetryState::new(true), http_error(code));
184 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
185 }
186
187 #[test_case(Code::Unavailable)]
188 #[test_case(Code::Internal)]
189 #[test_case(Code::ResourceExhausted)]
190 #[test_case(Code::DeadlineExceeded)]
191 fn retryable_grpc(code: Code) {
192 let p = RetryableErrors;
193 assert!(
194 p.on_error(&RetryState::new(true), grpc_error(code))
195 .is_continue()
196 );
197 assert!(
198 p.on_error(&RetryState::new(false), grpc_error(code))
199 .is_permanent()
200 );
201
202 let t = p.on_throttle(&RetryState::new(true), grpc_error(code));
203 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
204 }
205
206 #[test_case(Code::Unauthenticated)]
207 #[test_case(Code::PermissionDenied)]
208 fn not_recommended_grpc(code: Code) {
209 let p = RetryableErrors;
210 assert!(
211 p.on_error(&RetryState::new(true), grpc_error(code))
212 .is_permanent()
213 );
214 assert!(
215 p.on_error(&RetryState::new(false), grpc_error(code))
216 .is_permanent()
217 );
218
219 let t = p.on_throttle(&RetryState::new(true), grpc_error(code));
220 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
221 }
222
223 #[test]
224 fn io() {
225 let p = RetryableErrors;
226 assert!(p.on_error(&RetryState::new(true), io_error()).is_continue());
227 assert!(
228 p.on_error(&RetryState::new(false), io_error())
229 .is_permanent()
230 );
231
232 let t = p.on_throttle(&RetryState::new(true), io_error());
233 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
234 }
235
236 #[test]
237 fn timeout() {
238 let p = RetryableErrors;
239 assert!(
240 p.on_error(&RetryState::new(true), timeout_error())
241 .is_continue()
242 );
243 assert!(
244 p.on_error(&RetryState::new(false), timeout_error())
245 .is_permanent()
246 );
247
248 let t = p.on_throttle(&RetryState::new(true), timeout_error());
249 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
250 }
251
252 #[test]
253 fn continue_on_308() {
254 let inner: Arc<dyn RetryPolicy + 'static> = Arc::new(RetryableErrors);
255 let p = ContinueOn308::new(inner);
256 assert!(
257 p.on_error(&RetryState::new(true), http_error(308))
258 .is_continue()
259 );
260 assert!(
261 p.on_error(&RetryState::new(false), http_error(308))
262 .is_continue()
263 );
264
265 assert!(
266 p.on_error(&RetryState::new(true), http_error(429))
267 .is_continue()
268 );
269 assert!(
270 p.on_error(&RetryState::new(false), http_error(429))
271 .is_permanent()
272 );
273
274 let t = p.on_throttle(&RetryState::new(true), http_error(308));
275 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
276
277 let t = p.on_throttle(&RetryState::new(true), http_error(429));
278 assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
279 }
280
281 fn http_error(code: u16) -> Error {
282 Error::http(code, HeaderMap::new(), bytes::Bytes::new())
283 }
284
285 fn grpc_error(code: Code) -> Error {
286 let status = google_cloud_gax::error::rpc::Status::default().set_code(code);
287 Error::service(status)
288 }
289 fn timeout_error() -> Error {
290 Error::timeout(Status::deadline_exceeded("try again"))
291 }
292
293 fn io_error() -> Error {
294 Error::io(Status::unavailable("try again"))
295 }
296}