go-redis/redis · warning
redis: connection not available for write operation
Error message
redis: connection not available for write operation
What it means
errConnNotAvailableForWrite is a preallocated error (conn.go:28) returned by Conn.WithWriter when the underlying net.Conn is nil at write time (conn.go:1083-1090). It indicates the connection was concurrently closed/handed off while a write was attempted. As with the read variant, the pool treats this as a bad connection and the operation is retried on a new one.
Source
Thrown at internal/pool/conn.go:28
"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
- Rely on go-redis automatic retry for retriable commands.
- Retry idempotent writes at the application level if the error surfaces.
- Confirm RESP3 + maintenance notifications are enabled so writes during handoff are buffered/seamless.
Example fix
// before
err := client.Set(ctx, key, val, 0).Err()
if err != nil { return err }
// after
for i := 0; i < 3; i++ {
err := client.Set(ctx, key, val, 0).Err()
if err == nil { break }
if isBadConn(err, true) { continue }
} Defensive patterns
Strategy: retry
Validate before calling
// No deterministic pre-check (race with close). Tune pool + maintnotifications to minimize abrupt net.Conn removal during writes.
Try / catch
for i := 0; i < 3; i++ {
err := client.Set(ctx, key, val, ttl).Err()
if err == nil { break }
if isBadConn(err, true) { continue }
return err
} Prevention
- Enable RESP3 + maintenance notifications for graceful write-path handoff.
- Avoid writes during planned maintenance if possible.
- Size the pool to avoid emergency eviction mid-write.
When it happens
Trigger: A command write racing with connection close or maintenance-notification handoff; the netConn atomic slot is empty when SetWriteDeadline is attempted.
Common situations: Maintenance operations, abrupt connection eviction, or using a connection after the pool removed it under load.
Related errors
- redis: connection not available
- 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/85f93e778eb562b7.json.
Report an issue: GitHub.