go-redis/redis · warning
redis: timed out trying to mark connection as unusable
Error message
redis: timed out trying to mark connection as unusable
What it means
pool.ErrConnUnusableTimeout is returned when a connection was found unusable but the pool could not transition/remove it within the wait budget (pool.go:67-68). It indicates a race during connection removal under pressure — the pool timed out trying to mark the connection as unusable and instead surfaces this sentinel.
Source
Thrown at internal/pool/pool.go:68
// MetricStateUsed indicates the connection is currently being used
// by a client operation.
MetricStateUsed = "used"
)
var (
// ErrClosed performs any operation on the closed client will return this error.
ErrClosed = errors.New("redis: client is closed")
// ErrPoolExhausted is returned from a pool connection method
// when the maximum number of database connections in the pool has been reached.
ErrPoolExhausted = errors.New("redis: connection pool exhausted")
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
ErrPoolTimeout = errors.New("redis: connection pool timeout")
// ErrConnUnusableTimeout is returned when a connection is not usable and we timed out trying to mark it as unusable.
ErrConnUnusableTimeout = errors.New("redis: timed out trying to mark connection as unusable")
// errHookRequestedRemoval is returned when a hook requests connection removal.
errHookRequestedRemoval = errors.New("hook requested removal")
// errConnNotPooled is returned when trying to return a non-pooled connection to the pool.
errConnNotPooled = errors.New("connection not pooled")
// errConnEvictedIdle is passed to OnRemove hooks when a pooled connection is evicted on
// Put because the idle pool is already at MaxIdleConns.
errConnEvictedIdle = errors.New("connection evicted: idle pool at capacity")
// metricCallbackMu protects all global metric callback functions for thread-safe access.
metricCallbackMu sync.RWMutex
// Global metric callbacks for connection state changes
metricConnectionStateChangeCallback func(ctx context.Context, cn *Conn, fromState, toState string)
// Global metric callback for connection creation timeView on GitHub (pinned to 36d97525cd)
Solutions
- Treat as transient and retry the operation; the connection will be gone on the next attempt.
- Reduce pool churn — raise PoolSize/MinIdleConns so fewer emergency dials/removals happen.
- Check OnGet/OnPut hooks aren't unconditionally requesting removal.
Example fix
// before
return client.Get(ctx, key).Err()
// after
for i := 0; i < 3; i++ {
cmd := client.Get(ctx, key)
if err := cmd.Err(); err != nil {
if errors.Is(err, redis.ErrClosed) { break }
if isBadConn(err, false) { continue }
return err
}
return nil
} Defensive patterns
Strategy: retry
Validate before calling
// Transient removal race — no deterministic pre-check. // Reduce churn via adequate PoolSize/MinIdleConns and sane hooks.
Try / catch
for i := 0; i < 3; i++ {
err := client.Get(ctx, key).Err()
if err == nil { break }
if isBadConn(err, false) { continue }
return err
} Prevention
- Treat bad-conn errors as retriable for idempotent ops.
- Keep OnGet/OnPut hooks from unconditionally requesting removal.
- Raise pool size to lower emergency removal churn.
When it happens
Trigger: High-churn pool teardown interleaved with concurrent Get/Put, or maintnotifications forcibly removing connections while the pool is contended.
Common situations: Maintenance windows with heavy load, frequent connection eviction, or a custom OnGet/OnPut hook requesting removal under saturation.
Related errors
- redis: connection not available
- redis: connection not available for write operation
- redis: connection pool timeout
- relaxed timeout must be greater than 0
- post-handoff relaxed duration must be greater than or equal
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/fe0feb6357594ac1.json.
Report an issue: GitHub.