mirror of
				https://github.com/redis/go-redis.git
				synced 2025-11-04 02:33:24 +03:00 
			
		
		
		
	* fix(pool): wip, pool reauth should not interfere with handoff * fix credListeners map * fix race in tests * better conn usable timeout * add design decision comment * few small improvements * update marked as queued * add Used to clarify the state of the conn * rename test * fix(test): fix flaky test * lock inside the listeners collection * address pr comments * Update internal/auth/cred_listeners.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update internal/pool/buffer_size_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * wip refactor entraid * fix maintnotif pool hook * fix mocks * fix nil listener * sync and async reauth based on conn lifecycle * be able to reject connection OnGet * pass hooks so the tests can observe reauth * give some time for the background to execute commands * fix tests * only async reauth * Update internal/pool/pool.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update internal/auth/streaming/pool_hook.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update internal/pool/conn.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore(redisotel): use metric.WithAttributeSet to avoid copy (#3552) In order to improve performance replace `WithAttributes` with `WithAttributeSet`. This avoids the slice allocation and copy that is done in `WithAttributes`. For more information see https://github.com/open-telemetry/opentelemetry-go/blob/v1.38.0/metric/instrument.go#L357-L376 * chore(docs): explain why MaxRetries is disabled for ClusterClient (#3551) Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com> * exponential backoff * address pr comments * address pr comments * remove rlock * add some comments * add comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Warnar Boekkooi <wboekkooi@impossiblecloud.com> Co-authored-by: Justin <justindsouza80@gmail.com>
		
			
				
	
	
		
			190 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package redis
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/redis/go-redis/v9/internal"
 | 
						|
	"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
 | 
						|
 | 
						|
// ErrPoolExhausted is returned from a pool connection method
 | 
						|
// when the maximum number of database connections in the pool has been reached.
 | 
						|
var ErrPoolExhausted = pool.ErrPoolExhausted
 | 
						|
 | 
						|
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
 | 
						|
var ErrPoolTimeout = pool.ErrPoolTimeout
 | 
						|
 | 
						|
// ErrCrossSlot is returned when keys are used in the same Redis command and
 | 
						|
// the keys are not in the same hash slot. This error is returned by Redis
 | 
						|
// Cluster and will be returned by the client when TxPipeline or TxPipelined
 | 
						|
// is used on a ClusterClient with keys in different slots.
 | 
						|
var ErrCrossSlot = proto.RedisError("CROSSSLOT Keys in request don't hash to the same slot")
 | 
						|
 | 
						|
// 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 isContextError(err error) bool {
 | 
						|
	switch err {
 | 
						|
	case context.Canceled, context.DeadlineExceeded:
 | 
						|
		return true
 | 
						|
	default:
 | 
						|
		return false
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func shouldRetry(err error, retryTimeout bool) bool {
 | 
						|
	switch err {
 | 
						|
	case io.EOF, io.ErrUnexpectedEOF:
 | 
						|
		return true
 | 
						|
	case nil, context.Canceled, context.DeadlineExceeded:
 | 
						|
		return false
 | 
						|
	case pool.ErrPoolTimeout:
 | 
						|
		// connection pool timeout, increase retries. #3289
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	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, "MASTERDOWN ") {
 | 
						|
		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
 | 
						|
		case pool.ErrConnUnusableTimeout:
 | 
						|
			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:]
 | 
						|
	addr = internal.GetAddr(addr)
 | 
						|
	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
 | 
						|
}
 |