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

  1. Rely on go-redis automatic retry for retriable commands.
  2. Retry idempotent writes at the application level if the error surfaces.
  3. 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

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


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