google_cloud_bigquery/read/
retry_policy.rs1use google_cloud_gax::error::Error;
18use google_cloud_gax::error::rpc::Code;
19use google_cloud_gax::retry_policy::RetryPolicy;
20use google_cloud_gax::retry_result::RetryResult;
21use google_cloud_gax::retry_state::RetryState;
22
23#[derive(Clone, Debug, Default)]
50pub struct RetryableErrors;
51
52impl RetryPolicy for RetryableErrors {
53 fn on_error(&self, _state: &RetryState, error: Error) -> RetryResult {
54 if error.is_transient_and_before_rpc() {
55 return RetryResult::Continue(error);
56 }
57 if error.is_io() || error.is_timeout() || error.is_connect() {
58 return RetryResult::Continue(error);
59 }
60 if error.is_transport() && error.http_status_code().is_none() {
61 return RetryResult::Continue(error);
62 }
63 if let Some(429 | 500 | 502 | 503 | 504) = error.http_status_code() {
64 return RetryResult::Continue(error);
65 }
66 if let Some(status) = error.status() {
67 return match status.code {
68 Code::Aborted
69 | Code::Cancelled
70 | Code::DeadlineExceeded
71 | Code::Internal
72 | Code::ResourceExhausted
73 | Code::Unavailable => RetryResult::Continue(error),
74 _ => RetryResult::Permanent(error),
75 };
76 }
77 RetryResult::Permanent(error)
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use google_cloud_gax::error::CredentialsError;
85 use google_cloud_gax::error::rpc::Status;
86 use http::HeaderMap;
87 use test_case::test_case;
88
89 #[test_case(Code::Aborted)]
90 #[test_case(Code::Cancelled)]
91 #[test_case(Code::DeadlineExceeded)]
92 #[test_case(Code::Internal)]
93 #[test_case(Code::ResourceExhausted)]
94 #[test_case(Code::Unavailable)]
95 fn retryable_status_codes(code: Code) {
96 let err = || Error::service(Status::default().set_code(code).set_message("try again"));
97 assert!(matches!(
98 RetryableErrors.on_error(&RetryState::new(true), err()),
99 RetryResult::Continue(_)
100 ));
101 assert!(matches!(
103 RetryableErrors.on_error(&RetryState::new(false), err()),
104 RetryResult::Continue(_)
105 ));
106 }
107
108 #[test_case(Code::Unknown)]
109 #[test_case(Code::InvalidArgument)]
110 #[test_case(Code::NotFound)]
111 #[test_case(Code::PermissionDenied)]
112 #[test_case(Code::FailedPrecondition)]
113 #[test_case(Code::DataLoss)]
114 fn permanent_status_codes(code: Code) {
115 let err = Error::service(Status::default().set_code(code).set_message("fail"));
116 assert!(matches!(
117 RetryableErrors.on_error(&RetryState::new(true), err),
118 RetryResult::Permanent(_)
119 ));
120 }
121
122 #[test_case(429)]
123 #[test_case(500)]
124 #[test_case(502)]
125 #[test_case(503)]
126 #[test_case(504)]
127 fn retryable_http_status_codes(code: u16) {
128 let err = Error::http(code, HeaderMap::new(), bytes::Bytes::new());
129 assert!(matches!(
130 RetryableErrors.on_error(&RetryState::new(true), err),
131 RetryResult::Continue(_)
132 ));
133 }
134
135 #[test_case(400)]
136 #[test_case(401)]
137 #[test_case(403)]
138 #[test_case(404)]
139 #[test_case(408)]
140 #[test_case(409)]
141 #[test_case(501)]
142 fn permanent_http_status_codes(code: u16) {
143 let err = Error::http(code, HeaderMap::new(), bytes::Bytes::new());
144 assert!(matches!(
145 RetryableErrors.on_error(&RetryState::new(true), err),
146 RetryResult::Permanent(_)
147 ));
148 }
149
150 #[test]
151 fn retry_transient_before_rpc() {
152 let err = Error::authentication(CredentialsError::from_msg(true, "try again"));
153 assert!(matches!(
154 RetryableErrors.on_error(&RetryState::new(false), err),
155 RetryResult::Continue(_)
156 ));
157
158 let err = Error::authentication(CredentialsError::from_msg(false, "fail"));
159 assert!(matches!(
160 RetryableErrors.on_error(&RetryState::new(true), err),
161 RetryResult::Permanent(_)
162 ));
163 }
164
165 #[test]
166 fn retry_io_timeout_connect_transport() {
167 let state = RetryState::new(true);
168 assert!(
169 RetryableErrors
170 .on_error(&state, Error::io("io"))
171 .is_continue()
172 );
173 assert!(
174 RetryableErrors
175 .on_error(&state, Error::timeout("timeout"))
176 .is_continue()
177 );
178 assert!(
179 RetryableErrors
180 .on_error(&state, Error::connect("connect"))
181 .is_continue()
182 );
183 assert!(
184 RetryableErrors
185 .on_error(&state, Error::transport(HeaderMap::new(), "rst_stream"))
186 .is_continue()
187 );
188 }
189
190 #[test]
191 fn permanent_serde() {
192 let state = RetryState::new(true);
193 assert!(
194 RetryableErrors
195 .on_error(&state, Error::ser("fail"))
196 .is_permanent()
197 );
198 assert!(
199 RetryableErrors
200 .on_error(&state, Error::deser("fail"))
201 .is_permanent()
202 );
203 }
204}