go-redis/redis · error

max handoff retries reached

Error message

max handoff retries reached

What it means

Returned by performHandoffInternal (handoff_worker.go:407-412) when IncrementAndGetHandoffRetries exceeds Config.MaxHandoffRetries. Unlike other handoff errors this one is non-retryable (shouldRetry=false): the connection has failed to relocate to its new endpoint too many times and is removed from the pool rather than being queued again.

Solutions

  1. Verify the new endpoint is reachable from the client host (telnet/curl the host:port the MOVING notification advertised).
  2. Check EndpointType: if the server returns an internal IP/FQDN the client cannot route to, switch to an external type or fix network/DNS.
  3. If the endpoint is genuinely flaky but recoverable, raise Config.MaxHandoffRetries (1-10) to give handoff more attempts.
  4. Increase HandoffTimeout so transient slow dials don't each burn a retry.

Example fix

// before
cfg := maintnotifications.DefaultConfig() // MaxHandoffRetries: 3

// after
cfg := maintnotifications.DefaultConfig()
cfg.MaxHandoffRetries = 5
cfg.HandoffTimeout = 30 * time.Second
cfg.EndpointType = maintnotifications.EndpointTypeExternalIP
Defensive patterns

Strategy: retry

Try / catch

if errors.Is(err, maintnotifications.ErrMaxHandoffRetriesReached) {
    // connection was dropped; the next command will use a fresh pooled conn.
    // Investigate endpoint reachability / EndpointType, then retry the command.
}

Prevention

When it happens

Trigger: A connection receives a MOVING notification, is handed off, but the new endpoint is repeatedly unreachable or the dial keeps failing. Each retry increments the counter; once it crosses MaxHandoffRetries (default 3), performConnectionHandoff returns ErrMaxHandoffRetriesReached and processHandoffRequest closes the connection (handoff_worker.go:247-251).

Common situations: The new endpoint returned by MOVING is wrong/unreachable (firewall, DNS, wrong EndpointType for the network); the target node is down during a multi-shard migration storm; HandoffTimeout too short causing repeated dial aborts that each count as a retry.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /api/errors/b991147460021fb7. Report an issue: GitHub.

Appendix: source

Thrown at maintnotifications/errors.go:17

package maintnotifications

import (
	"errors"

	"github.com/redis/go-redis/v9/internal/maintnotifications/logs"
)

// Configuration errors
var (
	ErrInvalidRelaxedTimeout             = errors.New(logs.InvalidRelaxedTimeoutError())
	ErrInvalidHandoffTimeout             = errors.New(logs.InvalidHandoffTimeoutError())
	ErrInvalidHandoffWorkers             = errors.New(logs.InvalidHandoffWorkersError())
	ErrInvalidHandoffQueueSize           = errors.New(logs.InvalidHandoffQueueSizeError())
	ErrInvalidPostHandoffRelaxedDuration = errors.New(logs.InvalidPostHandoffRelaxedDurationError())
	ErrInvalidEndpointType               = errors.New(logs.InvalidEndpointTypeError())
	ErrInvalidMaintNotifications         = errors.New(logs.InvalidMaintNotificationsError())
	ErrMaxHandoffRetriesReached          = errors.New(logs.MaxHandoffRetriesReachedError())

	// Configuration validation errors

	// ErrInvalidHandoffRetries is returned when the number of handoff retries is invalid
	ErrInvalidHandoffRetries = errors.New(logs.InvalidHandoffRetriesError())
)

// Integration errors
var (
	// ErrInvalidClient is returned when the client does not support push notifications
	ErrInvalidClient = errors.New(logs.InvalidClientError())
)

// Handoff errors
var (
	// ErrHandoffQueueFull is returned when the handoff queue is full
	ErrHandoffQueueFull = errors.New(logs.HandoffQueueFullError())

View on GitHub (pinned to 36d97525cd)