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