common_meta/error/retry_hint/
mysql.rs1use common_error::ext::{RetryHint, retry_hint_from_io_error};
16
17const ER_CON_COUNT_ERROR: u16 = 1040;
27const ER_BAD_HOST_ERROR: u16 = 1042;
29const ER_HANDSHAKE_ERROR: u16 = 1043;
33const ER_UNKNOWN_COM_ERROR: u16 = 1047;
35const ER_ACCESS_DENIED_ERROR: u16 = 1045;
37const ER_BAD_DB_ERROR: u16 = 1049;
39const ER_SERVER_SHUTDOWN: u16 = 1053;
41const ER_FORCING_CLOSE: u16 = 1080;
43const ER_DUP_ENTRY: u16 = 1062;
45const ER_NO_SUCH_TABLE: u16 = 1146;
47
48const ER_ABORTING_CONNECTION: u16 = 1152;
50const ER_NET_PACKET_TOO_LARGE: u16 = 1153;
52const ER_NET_READ_ERROR_FROM_PIPE: u16 = 1154;
54const ER_NET_FCNTL_ERROR: u16 = 1155;
56const ER_NET_PACKETS_OUT_OF_ORDER: u16 = 1156;
58const ER_NET_UNCOMPRESS_ERROR: u16 = 1157;
60const ER_NET_READ_ERROR: u16 = 1158;
62const ER_NET_READ_INTERRUPTED: u16 = 1159;
64const ER_NET_ERROR_ON_WRITE: u16 = 1160;
66const ER_NET_WRITE_INTERRUPTED: u16 = 1161;
68const ER_NEW_ABORTING_CONNECTION: u16 = 1184;
70const ER_MASTER_NET_READ: u16 = 1189;
72const ER_MASTER_NET_WRITE: u16 = 1190;
74
75const ER_TOO_MANY_USER_CONNECTIONS: u16 = 1203;
79const ER_LOCK_WAIT_TIMEOUT: u16 = 1205;
81const ER_LOCK_DEADLOCK: u16 = 1213;
83const ER_CONNECT_TO_MASTER: u16 = 1218;
85const ER_USER_LIMIT_REACHED: u16 = 1226;
87const ER_NOT_SUPPORTED_AUTH_MODE: u16 = 1251;
89const ER_NET_OK_PACKET_TOO_LARGE: u16 = 3068;
91
92const CR_SERVER_GONE_ERROR: u16 = 2006;
94const CR_SERVER_LOST: u16 = 2013;
96
97fn retry_hint_from_mysql_database_error(number: Option<u16>, message: &str) -> RetryHint {
99 match number {
100 Some(
101 ER_CON_COUNT_ERROR
102 | ER_TOO_MANY_USER_CONNECTIONS
103 | self::ER_USER_LIMIT_REACHED
104 | ER_BAD_HOST_ERROR
105 | ER_SERVER_SHUTDOWN
106 | ER_FORCING_CLOSE
107 | ER_ABORTING_CONNECTION
108 | ER_NET_READ_ERROR_FROM_PIPE
109 | ER_NET_FCNTL_ERROR
110 | ER_NET_READ_ERROR
111 | ER_NET_READ_INTERRUPTED
112 | ER_NET_ERROR_ON_WRITE
113 | ER_NET_WRITE_INTERRUPTED
114 | ER_NEW_ABORTING_CONNECTION
115 | ER_MASTER_NET_READ
116 | ER_MASTER_NET_WRITE
117 | ER_LOCK_WAIT_TIMEOUT
118 | ER_LOCK_DEADLOCK
119 | ER_CONNECT_TO_MASTER
120 | CR_SERVER_GONE_ERROR
121 | CR_SERVER_LOST,
122 ) => return RetryHint::Retryable,
123 Some(
127 ER_HANDSHAKE_ERROR
128 | ER_UNKNOWN_COM_ERROR
129 | ER_ACCESS_DENIED_ERROR
130 | ER_BAD_DB_ERROR
131 | ER_DUP_ENTRY
132 | ER_NO_SUCH_TABLE
133 | ER_NET_PACKET_TOO_LARGE
134 | ER_NET_PACKETS_OUT_OF_ORDER
135 | ER_NET_UNCOMPRESS_ERROR
136 | ER_NOT_SUPPORTED_AUTH_MODE
137 | ER_NET_OK_PACKET_TOO_LARGE,
138 ) => return RetryHint::NonRetryable,
139 _ => {}
140 }
141
142 if is_mysql_serialization_database_error(message) {
143 RetryHint::Retryable
144 } else {
145 RetryHint::NonRetryable
146 }
147}
148
149fn is_mysql_serialization_database_error(message: &str) -> bool {
150 matches!(
151 message,
152 "Deadlock found when trying to get lock; try restarting transaction"
153 | "can't serialize access for this transaction"
154 )
155}
156
157pub fn is_mysql_serialization_error(error: &sqlx::Error) -> bool {
158 match error {
159 sqlx::Error::Database(error) => {
160 let mysql_error = error
161 .as_error()
162 .downcast_ref::<sqlx::mysql::MySqlDatabaseError>();
163 matches!(
164 mysql_error.map(|error| error.number()),
165 Some(ER_LOCK_WAIT_TIMEOUT | ER_LOCK_DEADLOCK)
166 ) || is_mysql_serialization_database_error(error.message())
167 }
168 _ => false,
169 }
170}
171
172pub fn retry_hint_from_sqlx_error(error: &sqlx::Error) -> RetryHint {
174 match error {
175 sqlx::Error::Io(error) => retry_hint_from_io_error(error),
176 sqlx::Error::Tls(_) | sqlx::Error::Protocol(_) => RetryHint::NonRetryable,
182 sqlx::Error::PoolTimedOut | sqlx::Error::WorkerCrashed => RetryHint::Retryable,
183 sqlx::Error::Database(error) => {
184 let mysql_error = error
185 .as_error()
186 .downcast_ref::<sqlx::mysql::MySqlDatabaseError>();
187 retry_hint_from_mysql_database_error(
188 mysql_error.map(|error| error.number()),
189 error.message(),
190 )
191 }
192 sqlx::Error::Configuration(_)
193 | sqlx::Error::InvalidArgument(_)
194 | sqlx::Error::RowNotFound
195 | sqlx::Error::TypeNotFound { .. }
196 | sqlx::Error::ColumnIndexOutOfBounds { .. }
197 | sqlx::Error::ColumnNotFound(_)
198 | sqlx::Error::ColumnDecode { .. }
199 | sqlx::Error::Encode(_)
200 | sqlx::Error::Decode(_)
201 | sqlx::Error::AnyDriverError(_)
202 | sqlx::Error::PoolClosed
203 | sqlx::Error::InvalidSavePointStatement
204 | sqlx::Error::BeginFailed => RetryHint::NonRetryable,
205 _ => RetryHint::NonRetryable,
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use common_error::ext::RetryHint;
212
213 use super::*;
214
215 #[test]
216 fn test_mysql_database_error_retry_hint() {
217 let retryable_numbers = [
218 ER_CON_COUNT_ERROR,
219 ER_TOO_MANY_USER_CONNECTIONS,
220 ER_USER_LIMIT_REACHED,
221 ER_BAD_HOST_ERROR,
222 ER_SERVER_SHUTDOWN,
223 ER_FORCING_CLOSE,
224 ER_ABORTING_CONNECTION,
225 ER_NET_READ_ERROR_FROM_PIPE,
226 ER_NET_FCNTL_ERROR,
227 ER_NET_READ_ERROR,
228 ER_NET_READ_INTERRUPTED,
229 ER_NET_ERROR_ON_WRITE,
230 ER_NET_WRITE_INTERRUPTED,
231 ER_NEW_ABORTING_CONNECTION,
232 ER_MASTER_NET_READ,
233 ER_MASTER_NET_WRITE,
234 ER_LOCK_WAIT_TIMEOUT,
235 ER_LOCK_DEADLOCK,
236 ER_CONNECT_TO_MASTER,
237 CR_SERVER_GONE_ERROR,
238 CR_SERVER_LOST,
239 ];
240
241 for number in retryable_numbers {
242 assert_eq!(
243 retry_hint_from_mysql_database_error(Some(number), "retryable mysql error"),
244 RetryHint::Retryable,
245 "errno {number} should be retryable"
246 );
247 }
248
249 let non_retryable_numbers = [
250 ER_HANDSHAKE_ERROR,
251 ER_UNKNOWN_COM_ERROR,
252 ER_ACCESS_DENIED_ERROR,
253 ER_BAD_DB_ERROR,
254 ER_DUP_ENTRY,
255 ER_NO_SUCH_TABLE,
256 ER_NET_PACKET_TOO_LARGE,
257 ER_NET_PACKETS_OUT_OF_ORDER,
258 ER_NET_UNCOMPRESS_ERROR,
259 ER_NOT_SUPPORTED_AUTH_MODE,
260 ER_NET_OK_PACKET_TOO_LARGE,
261 ];
262
263 for number in non_retryable_numbers {
264 assert_eq!(
265 retry_hint_from_mysql_database_error(Some(number), "non-retryable mysql error"),
266 RetryHint::NonRetryable,
267 "errno {number} should be non-retryable"
268 );
269 }
270 }
271
272 #[test]
273 fn test_mysql_database_error_message_fallback_retry_hint() {
274 assert_eq!(
275 retry_hint_from_mysql_database_error(
276 None,
277 "Deadlock found when trying to get lock; try restarting transaction",
278 ),
279 RetryHint::Retryable
280 );
281 assert_eq!(
282 retry_hint_from_mysql_database_error(
283 None,
284 "can't serialize access for this transaction",
285 ),
286 RetryHint::Retryable
287 );
288 assert_eq!(
289 retry_hint_from_mysql_database_error(None, "unknown mysql error"),
290 RetryHint::NonRetryable
291 );
292 assert_eq!(
293 retry_hint_from_mysql_database_error(Some(9999), "unknown mysql error"),
294 RetryHint::NonRetryable
295 );
296 }
297
298 #[test]
299 fn test_mysql_serialization_database_error() {
300 assert!(is_mysql_serialization_database_error(
301 "Deadlock found when trying to get lock; try restarting transaction",
302 ));
303 assert!(is_mysql_serialization_database_error(
304 "can't serialize access for this transaction"
305 ));
306 assert!(!is_mysql_serialization_database_error("duplicate entry"));
307 }
308}