| | | 1 | | using Microsoft.Data.SqlClient; |
| | | 2 | | |
| | | 3 | | namespace AsyncResponse.Internal; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Classifies SQL Server errors worth retrying. <see cref="SqlException"/> exposes no public |
| | | 7 | | /// transient flag, so this mirrors the error numbers Microsoft's own retry guidance and the |
| | | 8 | | /// SqlClient configurable-retry defaults treat as transient, plus severity ≥ 20 (broken connection). |
| | | 9 | | /// Source-linked into the SQL Server channel and transport packages (separate packages cannot share |
| | | 10 | | /// compiled code), so both retry the same set: a number curated in one copy and not the other would |
| | | 11 | | /// make the same fault permanent on one side of the same deployment. |
| | | 12 | | /// </summary> |
| | | 13 | | internal static class SqlServerTransientFaults |
| | | 14 | | { |
| | 2 | 15 | | private static readonly HashSet<int> TransientErrorNumbers = |
| | 2 | 16 | | [ |
| | 2 | 17 | | -2, // client-side command timeout |
| | 2 | 18 | | 20, // instance does not support encryption |
| | 2 | 19 | | 64, // connection lost during login |
| | 2 | 20 | | 121, // transport semaphore timeout |
| | 2 | 21 | | 233, // no process on the other end of the pipe |
| | 2 | 22 | | 997, // overlapped I/O in progress |
| | 2 | 23 | | 1204, // lock resources exhausted |
| | 2 | 24 | | 1205, // deadlock victim |
| | 2 | 25 | | 1222, // lock request timeout |
| | 2 | 26 | | 4060, // database unavailable |
| | 2 | 27 | | 4221, // readable secondary timeout |
| | 2 | 28 | | 10053, // transport-level connection abort |
| | 2 | 29 | | 10054, // transport-level connection reset |
| | 2 | 30 | | 10060, // network unreachable / connect timeout |
| | 2 | 31 | | 10928, // Azure SQL resource limit reached |
| | 2 | 32 | | 10929, // Azure SQL minimum guarantee exceeded |
| | 2 | 33 | | 40143, // Azure SQL connection failure |
| | 2 | 34 | | 40197, // Azure SQL service processing error |
| | 2 | 35 | | 40501, // Azure SQL service busy |
| | 2 | 36 | | 40540, // Azure SQL service unavailable |
| | 2 | 37 | | 40613, // Azure SQL database unavailable |
| | 2 | 38 | | 49918, // cannot process request, not enough resources |
| | 2 | 39 | | 49919, // cannot process create/update request |
| | 2 | 40 | | 49920 // cannot process request, too many operations |
| | 2 | 41 | | ]; |
| | | 42 | | |
| | | 43 | | /// <summary> |
| | | 44 | | /// The predicate the retry loops pass to <c>AsyncResponseRetry</c>. Cancellation is excluded |
| | | 45 | | /// first: a token trip is the caller's decision, never a fault to retry. |
| | | 46 | | /// </summary> |
| | | 47 | | public static bool IsTransient(Exception exception) |
| | 86 | 48 | | => exception is not OperationCanceledException |
| | 86 | 49 | | && (exception is SqlException sqlException && IsTransient(sqlException) |
| | 86 | 50 | | || exception is TimeoutException); |
| | | 51 | | |
| | | 52 | | public static bool IsTransient(SqlException exception) |
| | | 53 | | { |
| | 74 | 54 | | if (exception.Class >= 20) |
| | 6 | 55 | | return true; |
| | | 56 | | |
| | 218 | 57 | | foreach (SqlError error in exception.Errors) |
| | | 58 | | { |
| | 68 | 59 | | if (TransientErrorNumbers.Contains(error.Number)) |
| | 54 | 60 | | return true; |
| | | 61 | | } |
| | | 62 | | |
| | 14 | 63 | | return TransientErrorNumbers.Contains(exception.Number); |
| | 54 | 64 | | } |
| | | 65 | | } |