@@ -3,6 +3,7 @@ package rediscluster
33import (
44 "bytes"
55 "math"
6+ "strconv"
67 "sync/atomic"
78 "time"
89
@@ -142,7 +143,7 @@ func (c *Cluster) updateMappings(slotRanges []redisclusterutil.SlotsRange) {
142143 c .nodeWait .promises = make (map [string ]* []connThen , 1 )
143144 c .nodeWait .Unlock ()
144145
145- go newConfig .setConnRoles ()
146+ go newConfig .setConnRoles (c . opts . ReplicaLinkDownTolerance )
146147
147148 var sh uint32
148149 for i := 0 ; i < redisclusterutil .NumSlots ; i ++ {
@@ -213,7 +214,13 @@ func (c *Cluster) updateMappings(slotRanges []redisclusterutil.SlotsRange) {
213214 })
214215}
215216
216- func (s * shard ) setReplicaInfo (res interface {}, n uint64 ) {
217+ func (s * shard ) replicaInfoFuture (linkDownTolerance time.Duration ) redis.FuncFuture {
218+ return func (res interface {}, n uint64 ) {
219+ s .setReplicaInfo (res , n , linkDownTolerance )
220+ }
221+ }
222+
223+ func (s * shard ) setReplicaInfo (res interface {}, n uint64 , linkDownTolerance time.Duration ) {
217224 haserr := false
218225 if err := redis .AsError (res ); err != nil {
219226 haserr = true
@@ -222,8 +229,8 @@ func (s *shard) setReplicaInfo(res interface{}, n uint64) {
222229 haserr = ! (ok && str == "OK" )
223230 } else if buf , ok := res .([]byte ); ! ok {
224231 haserr = true
225- } else if bytes . Contains ( buf , [] byte ( "master_link_status:down" )) || bytes . Contains ( buf , [] byte ( "loading:1" )) {
226- haserr = true
232+ } else {
233+ haserr = ! replicaHealthy ( buf , linkDownTolerance )
227234 }
228235 for {
229236 oldstate := atomic .LoadUint32 (& s .good )
@@ -242,7 +249,59 @@ func (s *shard) setReplicaInfo(res interface{}, n uint64) {
242249 }
243250}
244251
245- func (cfg * clusterConfig ) setConnRoles () {
252+ // replicaHealthy tells whether INFO output describes a replica worth reading from.
253+ // master_link_down_since_seconds is -1 for a replica that has never synced since it
254+ // started, and its dataset is then anything from empty to the RDB it booted from.
255+ func replicaHealthy (info []byte , linkDownTolerance time.Duration ) bool {
256+ if infoHas (info , "loading" , "1" ) {
257+ return false
258+ }
259+ if ! infoHas (info , "master_link_status" , "down" ) {
260+ return true
261+ }
262+ since , ok := infoInt (info , "master_link_down_since_seconds" )
263+ if ! ok || since < 0 {
264+ return false
265+ }
266+ return time .Duration (since )* time .Second < linkDownTolerance
267+ }
268+
269+ // infoField returns the value of a named INFO field. The name is matched as a whole
270+ // line prefix: `loading` must not be found inside `async_loading`.
271+ func infoField (info []byte , field string ) ([]byte , bool ) {
272+ key := []byte (field + ":" )
273+ for off := 0 ; ; {
274+ i := bytes .Index (info [off :], key )
275+ if i < 0 {
276+ return nil , false
277+ }
278+ i += off
279+ if i == 0 || info [i - 1 ] == '\n' {
280+ value := info [i + len (key ):]
281+ if end := bytes .IndexAny (value , "\r \n " ); end >= 0 {
282+ value = value [:end ]
283+ }
284+ return value , true
285+ }
286+ off = i + 1
287+ }
288+ }
289+
290+ func infoHas (info []byte , field , value string ) bool {
291+ got , ok := infoField (info , field )
292+ return ok && string (got ) == value
293+ }
294+
295+ func infoInt (info []byte , field string ) (int64 , bool ) {
296+ value , ok := infoField (info , field )
297+ if ! ok {
298+ return 0 , false
299+ }
300+ v , err := strconv .ParseInt (string (value ), 10 , 64 )
301+ return v , err == nil
302+ }
303+
304+ func (cfg * clusterConfig ) setConnRoles (linkDownTolerance time.Duration ) {
246305 for _ , sh := range cfg .shards {
247306 for i , addr := range sh .addr {
248307 node := cfg .nodes [addr ]
@@ -254,7 +313,7 @@ func (cfg *clusterConfig) setConnRoles() {
254313 conn .Send (Request {"READWRITE" , nil }, nil , 0 )
255314 } else {
256315 conn .SendBatch ([]Request {{"READONLY" , nil }, {"INFO" , nil }},
257- redis . FuncFuture ( sh .setReplicaInfo ), uint64 (i * 2 ))
316+ sh .replicaInfoFuture ( linkDownTolerance ), uint64 (i * 2 ))
258317 }
259318 }
260319 }
0 commit comments