error.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package redis
  2. import (
  3. "context"
  4. "io"
  5. "net"
  6. "strings"
  7. "github.com/go-redis/redis/v8/internal/pool"
  8. "github.com/go-redis/redis/v8/internal/proto"
  9. )
  10. // ErrClosed performs any operation on the closed client will return this error.
  11. var ErrClosed = pool.ErrClosed
  12. type Error interface {
  13. error
  14. // RedisError is a no-op function but
  15. // serves to distinguish types that are Redis
  16. // errors from ordinary errors: a type is a
  17. // Redis error if it has a RedisError method.
  18. RedisError()
  19. }
  20. var _ Error = proto.RedisError("")
  21. func shouldRetry(err error, retryTimeout bool) bool {
  22. switch err {
  23. case io.EOF, io.ErrUnexpectedEOF:
  24. return true
  25. case nil, context.Canceled, context.DeadlineExceeded:
  26. return false
  27. }
  28. if v, ok := err.(timeoutError); ok {
  29. if v.Timeout() {
  30. return retryTimeout
  31. }
  32. return true
  33. }
  34. s := err.Error()
  35. if s == "ERR max number of clients reached" {
  36. return true
  37. }
  38. if strings.HasPrefix(s, "LOADING ") {
  39. return true
  40. }
  41. if strings.HasPrefix(s, "READONLY ") {
  42. return true
  43. }
  44. if strings.HasPrefix(s, "CLUSTERDOWN ") {
  45. return true
  46. }
  47. if strings.HasPrefix(s, "TRYAGAIN ") {
  48. return true
  49. }
  50. return false
  51. }
  52. func isRedisError(err error) bool {
  53. _, ok := err.(proto.RedisError)
  54. return ok
  55. }
  56. func isBadConn(err error, allowTimeout bool, addr string) bool {
  57. switch err {
  58. case nil:
  59. return false
  60. case context.Canceled, context.DeadlineExceeded:
  61. return true
  62. }
  63. if isRedisError(err) {
  64. switch {
  65. case isReadOnlyError(err):
  66. // Close connections in read only state in case domain addr is used
  67. // and domain resolves to a different Redis Server. See #790.
  68. return true
  69. case isMovedSameConnAddr(err, addr):
  70. // Close connections when we are asked to move to the same addr
  71. // of the connection. Force a DNS resolution when all connections
  72. // of the pool are recycled
  73. return true
  74. default:
  75. return false
  76. }
  77. }
  78. if allowTimeout {
  79. if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
  80. return !netErr.Temporary()
  81. }
  82. }
  83. return true
  84. }
  85. func isMovedError(err error) (moved bool, ask bool, addr string) {
  86. if !isRedisError(err) {
  87. return
  88. }
  89. s := err.Error()
  90. switch {
  91. case strings.HasPrefix(s, "MOVED "):
  92. moved = true
  93. case strings.HasPrefix(s, "ASK "):
  94. ask = true
  95. default:
  96. return
  97. }
  98. ind := strings.LastIndex(s, " ")
  99. if ind == -1 {
  100. return false, false, ""
  101. }
  102. addr = s[ind+1:]
  103. return
  104. }
  105. func isLoadingError(err error) bool {
  106. return strings.HasPrefix(err.Error(), "LOADING ")
  107. }
  108. func isReadOnlyError(err error) bool {
  109. return strings.HasPrefix(err.Error(), "READONLY ")
  110. }
  111. func isMovedSameConnAddr(err error, addr string) bool {
  112. redisError := err.Error()
  113. if !strings.HasPrefix(redisError, "MOVED ") {
  114. return false
  115. }
  116. return strings.HasSuffix(redisError, " "+addr)
  117. }
  118. //------------------------------------------------------------------------------
  119. type timeoutError interface {
  120. Timeout() bool
  121. }