| 82 | } |
| 83 | |
| 84 | func shouldRetry(err error, retryTimeout bool) bool { |
| 85 | if err == nil { |
| 86 | return false |
| 87 | } |
| 88 | |
| 89 | // Check for EOF errors (works with wrapped errors) |
| 90 | if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { |
| 91 | return true |
| 92 | } |
| 93 | |
| 94 | // Dial errors mean TCP connection was never established — safe to retry even |
| 95 | // when wrapped inside context.DeadlineExceeded (from DialTimeout context). |
| 96 | // Must be checked before the context error check below. |
| 97 | var opErr *net.OpError |
| 98 | if errors.As(err, &opErr) && opErr.Op == "dial" { |
| 99 | return true |
| 100 | } |
| 101 | |
| 102 | // Check for context errors (works with wrapped errors) |
| 103 | if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { |
| 104 | return false |
| 105 | } |
| 106 | |
| 107 | // Check for pool timeout (works with wrapped errors) |
| 108 | if errors.Is(err, pool.ErrPoolTimeout) { |
| 109 | // connection pool timeout, increase retries. #3289 |
| 110 | return true |
| 111 | } |
| 112 | |
| 113 | // Check for timeout errors (works with wrapped errors) |
| 114 | if isTimeout, hasTimeoutFlag := isTimeoutError(err); isTimeout { |
| 115 | if hasTimeoutFlag { |
| 116 | return retryTimeout |
| 117 | } |
| 118 | return true |
| 119 | } |
| 120 | |
| 121 | // Check for typed Redis errors using errors.As (works with wrapped errors) |
| 122 | if proto.IsMaxClientsError(err) { |
| 123 | return true |
| 124 | } |
| 125 | if proto.IsLoadingError(err) { |
| 126 | return true |
| 127 | } |
| 128 | if proto.IsReadOnlyError(err) { |
| 129 | return true |
| 130 | } |
| 131 | if proto.IsMasterDownError(err) { |
| 132 | return true |
| 133 | } |
| 134 | if proto.IsClusterDownError(err) { |
| 135 | return true |
| 136 | } |
| 137 | if proto.IsTryAgainError(err) { |
| 138 | return true |
| 139 | } |
| 140 | if proto.IsNoReplicasError(err) { |
| 141 | return true |