go-redis/redis · warning

redis: connection not available

Error message

redis: connection not available

What it means

errConnectionNotAvailable is a preallocated hot-path error (conn.go:27) returned by Conn.WithReader and WithReaderHardDeadline when the underlying net.Conn has been removed (nil) from the atomic wrapper. This happens when a connection was concurrently closed, marked for handoff, or torn down by maintenance notifications while a read is in flight. The pool treats it as a bad-conn error and typically retries on a fresh connection.

Source

Thrown at internal/pool/conn.go:27

	"net"
	"sync"
	"sync/atomic"
	"time"

	"github.com/redis/go-redis/v9/internal"
	"github.com/redis/go-redis/v9/internal/maintnotifications/logs"
	"github.com/redis/go-redis/v9/internal/proto"
	uberatomic "go.uber.org/atomic"
)

var noDeadline = time.Time{}

// Preallocated errors for hot paths to avoid allocations
var (
	errAlreadyMarkedForHandoff  = errors.New("connection is already marked for handoff")
	errNotMarkedForHandoff      = errors.New("connection was not marked for handoff")
	errHandoffStateChanged      = errors.New("handoff state changed during marking")
	errConnectionNotAvailable   = errors.New("redis: connection not available")
	errConnNotAvailableForWrite = errors.New("redis: connection not available for write operation")
)

// getCachedTimeNs returns the current time in nanoseconds.
// This function previously used a global cache updated by a background goroutine,
// but that caused unnecessary CPU usage when the client was idle (ticker waking up
// the scheduler every 50ms). We now use time.Now() directly, which is fast enough
// on modern systems (vDSO on Linux) and only adds ~1-2% overhead in extreme
// high-concurrency benchmarks while eliminating idle CPU usage.
func getCachedTimeNs() int64 {
	return time.Now().UnixNano()
}

// GetCachedTimeNs returns the current time in nanoseconds.
// Exported for use by other packages that need fast time access.
func GetCachedTimeNs() int64 {
	return getCachedTimeNs()
}

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Let go-redis retry automatically — this error is transient and the cmd will be retried on a new connection for retriable commands.
  2. If surfacing to the caller, retry the operation idempotently.
  3. Ensure maintenance notifications are enabled (Protocol 3, ModeAuto) so handoff is seamless rather than abrupt.

Example fix

// retry on transient connection-unavailable
for i := 0; i < 3; i++ {
    err := client.Get(ctx, key).Err()
    if err == nil { break }
    if errors.Is(err, pool.errConnectionNotAvailable) { continue }
}
Defensive patterns

Strategy: retry

Validate before calling

// No pre-check possible (concurrent close). Ensure maintnotifications + RESP3 enabled
// so handoff is seamless rather than abruptly removing the net.Conn.

Try / catch

for i := 0; i < 3; i++ {
    if err := client.Get(ctx, key).Err(); err != nil {
        if isBadConn(err, false) { continue }
        return err
    }
    break
}

Prevention

When it happens

Trigger: A read (response read) racing with connection close/handoff in the maintenance-notifications path, or a stale connection being used after Close(). Surfaced via WithReader/WithReaderHardDeadline (conn.go:1041-1062).

Common situations: Maintenance window (MIGRATING/FAILING_OVER) on Redis Cloud/Cluster where the connection is handed off mid-operation, or high churn with frequent connection eviction.

Related errors


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