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
- Let go-redis retry automatically — this error is transient and the cmd will be retried on a new connection for retriable commands.
- If surfacing to the caller, retry the operation idempotently.
- 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
- Enable maintenance notifications (Protocol 3, ModeAuto) for seamless handoff.
- Keep MinIdleConns > 0 so replacement connections exist.
- Don't hold connections across long blocking calls.
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
- redis: connection not available for write operation
- redis: timed out trying to mark connection as unusable
- connection marked for handoff
- connection marked for handoff with state
- max handoff retries reached
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/639938f3d00d1f33.json.
Report an issue: GitHub.