...

Source file src/github.com/redis/go-redis/v9/error.go

Documentation: github.com/redis/go-redis/v9

     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  // ErrClosed performs any operation on the closed client will return this error.
    16  var ErrClosed = pool.ErrClosed
    17  
    18  // ErrPoolExhausted is returned from a pool connection method
    19  // when the maximum number of database connections in the pool has been reached.
    20  var ErrPoolExhausted = pool.ErrPoolExhausted
    21  
    22  // ErrPoolTimeout timed out waiting to get a connection from the connection pool.
    23  var ErrPoolTimeout = pool.ErrPoolTimeout
    24  
    25  // ErrCrossSlot is returned when keys are used in the same Redis command and
    26  // the keys are not in the same hash slot. This error is returned by Redis
    27  // Cluster and will be returned by the client when TxPipeline or TxPipelined
    28  // is used on a ClusterClient with keys in different slots.
    29  var ErrCrossSlot = proto.RedisError("CROSSSLOT Keys in request don't hash to the same slot")
    30  
    31  // HasErrorPrefix checks if the err is a Redis error and the message contains a prefix.
    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 ") // KVRocks adds such prefix
    39  	return strings.HasPrefix(msg, prefix)
    40  }
    41  
    42  type Error interface {
    43  	error
    44  
    45  	// RedisError is a no-op function but
    46  	// serves to distinguish types that are Redis
    47  	// errors from ordinary errors: a type is a
    48  	// Redis error if it has a RedisError method.
    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  		// connection pool timeout, increase retries. #3289
    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  			// Close connections in read only state in case domain addr is used
   121  			// and domain resolves to a different Redis Server. See #790.
   122  			return true
   123  		case isMovedSameConnAddr(err, addr):
   124  			// Close connections when we are asked to move to the same addr
   125  			// of the connection. Force a DNS resolution when all connections
   126  			// of the pool are recycled
   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