...
1 package redis
2
3 import (
4 "context"
5 "errors"
6 "io"
7 "net"
8 "strings"
9
10 "github.com/redis/go-redis/v9/internal"
11 "github.com/redis/go-redis/v9/internal/pool"
12 "github.com/redis/go-redis/v9/internal/proto"
13 )
14
15
16 var ErrClosed = pool.ErrClosed
17
18
19
20 var ErrPoolExhausted = pool.ErrPoolExhausted
21
22
23 var ErrPoolTimeout = pool.ErrPoolTimeout
24
25
26
27
28
29 var ErrCrossSlot = proto.RedisError("CROSSSLOT Keys in request don't hash to the same slot")
30
31
32 func HasErrorPrefix(err error, prefix string) bool {
33 var rErr Error
34 if !errors.As(err, &rErr) {
35 return false
36 }
37 msg := rErr.Error()
38 msg = strings.TrimPrefix(msg, "ERR ")
39 return strings.HasPrefix(msg, prefix)
40 }
41
42 type Error interface {
43 error
44
45
46
47
48
49 RedisError()
50 }
51
52 var _ Error = proto.RedisError("")
53
54 func isContextError(err error) bool {
55 switch err {
56 case context.Canceled, context.DeadlineExceeded:
57 return true
58 default:
59 return false
60 }
61 }
62
63 func shouldRetry(err error, retryTimeout bool) bool {
64 switch err {
65 case io.EOF, io.ErrUnexpectedEOF:
66 return true
67 case nil, context.Canceled, context.DeadlineExceeded:
68 return false
69 case pool.ErrPoolTimeout:
70
71 return true
72 }
73
74 if v, ok := err.(timeoutError); ok {
75 if v.Timeout() {
76 return retryTimeout
77 }
78 return true
79 }
80
81 s := err.Error()
82 if s == "ERR max number of clients reached" {
83 return true
84 }
85 if strings.HasPrefix(s, "LOADING ") {
86 return true
87 }
88 if strings.HasPrefix(s, "READONLY ") {
89 return true
90 }
91 if strings.HasPrefix(s, "MASTERDOWN ") {
92 return true
93 }
94 if strings.HasPrefix(s, "CLUSTERDOWN ") {
95 return true
96 }
97 if strings.HasPrefix(s, "TRYAGAIN ") {
98 return true
99 }
100
101 return false
102 }
103
104 func isRedisError(err error) bool {
105 _, ok := err.(proto.RedisError)
106 return ok
107 }
108
109 func isBadConn(err error, allowTimeout bool, addr string) bool {
110 switch err {
111 case nil:
112 return false
113 case context.Canceled, context.DeadlineExceeded:
114 return true
115 }
116
117 if isRedisError(err) {
118 switch {
119 case isReadOnlyError(err):
120
121
122 return true
123 case isMovedSameConnAddr(err, addr):
124
125
126
127 return true
128 default:
129 return false
130 }
131 }
132
133 if allowTimeout {
134 if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
135 return false
136 }
137 }
138
139 return true
140 }
141
142 func isMovedError(err error) (moved bool, ask bool, addr string) {
143 if !isRedisError(err) {
144 return
145 }
146
147 s := err.Error()
148 switch {
149 case strings.HasPrefix(s, "MOVED "):
150 moved = true
151 case strings.HasPrefix(s, "ASK "):
152 ask = true
153 default:
154 return
155 }
156
157 ind := strings.LastIndex(s, " ")
158 if ind == -1 {
159 return false, false, ""
160 }
161
162 addr = s[ind+1:]
163 addr = internal.GetAddr(addr)
164 return
165 }
166
167 func isLoadingError(err error) bool {
168 return strings.HasPrefix(err.Error(), "LOADING ")
169 }
170
171 func isReadOnlyError(err error) bool {
172 return strings.HasPrefix(err.Error(), "READONLY ")
173 }
174
175 func isMovedSameConnAddr(err error, addr string) bool {
176 redisError := err.Error()
177 if !strings.HasPrefix(redisError, "MOVED ") {
178 return false
179 }
180 return strings.HasSuffix(redisError, " "+addr)
181 }
182
183
184
185 type timeoutError interface {
186 Timeout() bool
187 }
188
View as plain text