package redis import ( "context" "errors" "io" "net" "strings" "github.com/redis/go-redis/v9/internal/pool" "github.com/redis/go-redis/v9/internal/proto" ) // ErrClosed performs any operation on the closed client will return this error. var ErrClosed = pool.ErrClosed // HasErrorPrefix checks if the err is a Redis error and the message contains a prefix. func HasErrorPrefix(err error, prefix string) bool { var rErr Error if !errors.As(err, &rErr) { return false } msg := rErr.Error() msg = strings.TrimPrefix(msg, "ERR ") // KVRocks adds such prefix return strings.HasPrefix(msg, prefix) } type Error interface { error // RedisError is a no-op function but // serves to distinguish types that are Redis // errors from ordinary errors: a type is a // Redis error if it has a RedisError method. RedisError() } var _ Error = proto.RedisError("") func shouldRetry(err error, retryTimeout bool) bool { switch err { case io.EOF, io.ErrUnexpectedEOF: return true case nil, context.Canceled, context.DeadlineExceeded: return false } if v, ok := err.(timeoutError); ok { if v.Timeout() { return retryTimeout } return true } s := err.Error() if s == "ERR max number of clients reached" { return true } if strings.HasPrefix(s, "LOADING ") { return true } if strings.HasPrefix(s, "READONLY ") { return true } if strings.HasPrefix(s, "CLUSTERDOWN ") { return true } if strings.HasPrefix(s, "TRYAGAIN ") { return true } return false } func isRedisError(err error) bool { _, ok := err.(proto.RedisError) return ok } func isBadConn(err error, allowTimeout bool, addr string) bool { switch err { case nil: return false case context.Canceled, context.DeadlineExceeded: return true } if isRedisError(err) { switch { case isReadOnlyError(err): // Close connections in read only state in case domain addr is used // and domain resolves to a different Redis Server. See #790. return true case isMovedSameConnAddr(err, addr): // Close connections when we are asked to move to the same addr // of the connection. Force a DNS resolution when all connections // of the pool are recycled return true default: return false } } if allowTimeout { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { return false } } return true } func isMovedError(err error) (moved bool, ask bool, addr string) { if !isRedisError(err) { return } s := err.Error() switch { case strings.HasPrefix(s, "MOVED "): moved = true case strings.HasPrefix(s, "ASK "): ask = true default: return } ind := strings.LastIndex(s, " ") if ind == -1 { return false, false, "" } addr = s[ind+1:] return } func isLoadingError(err error) bool { return strings.HasPrefix(err.Error(), "LOADING ") } func isReadOnlyError(err error) bool { return strings.HasPrefix(err.Error(), "READONLY ") } func isMovedSameConnAddr(err error, addr string) bool { redisError := err.Error() if !strings.HasPrefix(redisError, "MOVED ") { return false } return strings.HasSuffix(redisError, " "+addr) } //------------------------------------------------------------------------------ type timeoutError interface { Timeout() bool }