{"id":"fe0feb6357594ac1","repo":"go-redis/redis","slug":"redis-timed-out-trying-to-mark-connection-as-unus","errorCode":null,"errorMessage":"redis: timed out trying to mark connection as unusable","messagePattern":"redis: timed out trying to mark connection as unusable","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/pool/pool.go","lineNumber":68,"sourceCode":"\n\t// MetricStateUsed indicates the connection is currently being used\n\t// by a client operation.\n\tMetricStateUsed = \"used\"\n)\n\nvar (\n\t// ErrClosed performs any operation on the closed client will return this error.\n\tErrClosed = errors.New(\"redis: client is closed\")\n\n\t// ErrPoolExhausted is returned from a pool connection method\n\t// when the maximum number of database connections in the pool has been reached.\n\tErrPoolExhausted = errors.New(\"redis: connection pool exhausted\")\n\n\t// ErrPoolTimeout timed out waiting to get a connection from the connection pool.\n\tErrPoolTimeout = errors.New(\"redis: connection pool timeout\")\n\n\t// ErrConnUnusableTimeout is returned when a connection is not usable and we timed out trying to mark it as unusable.\n\tErrConnUnusableTimeout = errors.New(\"redis: timed out trying to mark connection as unusable\")\n\n\t// errHookRequestedRemoval is returned when a hook requests connection removal.\n\terrHookRequestedRemoval = errors.New(\"hook requested removal\")\n\n\t// errConnNotPooled is returned when trying to return a non-pooled connection to the pool.\n\terrConnNotPooled = errors.New(\"connection not pooled\")\n\n\t// errConnEvictedIdle is passed to OnRemove hooks when a pooled connection is evicted on\n\t// Put because the idle pool is already at MaxIdleConns.\n\terrConnEvictedIdle = errors.New(\"connection evicted: idle pool at capacity\")\n\n\t// metricCallbackMu protects all global metric callback functions for thread-safe access.\n\tmetricCallbackMu sync.RWMutex\n\n\t// Global metric callbacks for connection state changes\n\tmetricConnectionStateChangeCallback func(ctx context.Context, cn *Conn, fromState, toState string)\n\n\t// Global metric callback for connection creation time","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/internal/pool/pool.go#L50-L86","documentation":"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.","triggerScenarios":"High-churn pool teardown interleaved with concurrent Get/Put, or maintnotifications forcibly removing connections while the pool is contended.","commonSituations":"Maintenance windows with heavy load, frequent connection eviction, or a custom OnGet/OnPut hook requesting removal under saturation.","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."],"exampleFix":"// before\nreturn client.Get(ctx, key).Err()\n// after\nfor i := 0; i < 3; i++ {\n    cmd := client.Get(ctx, key)\n    if err := cmd.Err(); err != nil {\n        if errors.Is(err, redis.ErrClosed) { break }\n        if isBadConn(err, false) { continue }\n        return err\n    }\n    return nil\n}","handlingStrategy":"retry","validationCode":"// Transient removal race — no deterministic pre-check.\n// Reduce churn via adequate PoolSize/MinIdleConns and sane hooks.","typeGuard":null,"tryCatchPattern":"for i := 0; i < 3; i++ {\n    err := client.Get(ctx, key).Err()\n    if err == nil { break }\n    if isBadConn(err, false) { continue }\n    return err\n}","preventionTips":["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."],"tags":["pool","timeout","transient","retry","maintnotifications"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}