go-redis/redis · error
redis: autopipeline: Close timed out after %s with %s still
Error message
redis: autopipeline: Close timed out after %s with %s still in flight; they hold pooled connections until the server or the OS ends them (most often a blocking command with no timeout, or ReadTimeout disabled)
What it means
Returned by AutoPipeliner.Close() when its single bounded drain (autoPipelineCloseBackstop) expires before every in-flight flush, batch dispatch, and diverted blocking command finishes. Close cannot cancel in-flight dispatches once commands are accepted, so a blocking command with no timeout, or a stalled read against a dead peer with ReadTimeout disabled, has nothing to end it; the bound returns this error instead of hanging forever. The engine is already closed to new work — leaked goroutines end when the server or OS tears down the connection.
Source
Thrown at autopipeline.go:1690
if !batchesDone {
// Name the precise stage: a wedged flusher and a wedged batch
// dispatch need different operator responses.
select {
case <-flushers:
select {
case <-swept:
outstanding = append(outstanding, "batch dispatches")
default:
outstanding = append(outstanding, "the shutdown flush")
}
default:
outstanding = append(outstanding, "the flusher drain")
}
}
if !divertedDone {
outstanding = append(outstanding, "diverted (blocking) commands")
}
return fmt.Errorf(
"redis: autopipeline: Close timed out after %s with %s still in flight; "+
"they hold pooled connections until the server or the OS ends them "+
"(most often a blocking command with no timeout, or ReadTimeout disabled)",
timeout, strings.Join(outstanding, " and "))
}
}
return nil
}
// flusher is the per-shard background goroutine that flushes batches.
func (s *apShard) flusher() {
defer s.ap.wg.Done()
ap := s.ap
for {
// Wait for a command to arrive (or shutdown). The notify channel is a
// cheap buffered wake-up; no lock is taken on the hot enqueue path.
if s.Len() == 0 {View on GitHub (pinned to 36d97525cd)
Solutions
- Set a finite ReadTimeout (and/or a per-command timeout) so stalled reads cannot outlive Close.
- Stop consumer goroutines and wait for them to return BEFORE calling Close(), so no blocking command is still in flight.
- Issue blocking commands with an explicit finite BLOCK timeout (e.g. XREAD BLOCK 5000) instead of BLOCK 0.
- On this error, treat the client as closed (no new work accepted) and exit the process or rotate the connection; do not retry Close in a tight loop.
Example fix
// before
opts := redis.Options{Addr: addr /* ReadTimeout: 0 default */}
rdb := redis.NewClient(&opts)
// ... a goroutine runs BLPOP with no timeout ...
rdb.Close() // may time out
// after
opts := redis.Options{
Addr: addr,
ReadTimeout: 5 * time.Second,
}
// consumer issues BLPOP with a finite timeout; cancel its context before Close
cancel()
consumerWg.Wait()
if err := rdb.Close(); err != nil {
log.Printf("close returned: %v (in-flight ops will drain when conn closes)", err)
} Defensive patterns
Strategy: try-catch
Try / catch
if err := rdb.Close(); err != nil {
// Engine is closed to new work; in-flight ops end when the conn is torn down.
// Do NOT loop retrying Close. Log and rotate/exit.
if strings.Contains(err.Error(), "Close timed out") {
log.Printf("redis close timed out: %v", err)
} else {
return err
}
} Prevention
- Always set a finite ReadTimeout on clients that issue blocking commands.
- Stop consumer goroutines and wg.Wait() BEFORE calling Close().
- Prefer finite BLOCK timeouts (e.g. XREAD BLOCK 5000) over BLOCK 0.
- Never background-poll Close in a retry loop — on this error the engine is already closed.
When it happens
Trigger: Calling Close() while a diverted blocking command (BLPOP/XREAD with BLOCK 0) is in flight, or while a flush is stuck reading from a dead server and the client has ReadTimeout: 0 (disabled). The timer at autopipeline.go:1670 fires before batchesDone/divertedDone both complete.
Common situations: Long-running consumer goroutines issuing BLPOP with no timeout then shutting down the client; ReadTimeout disabled for latency-sensitive workloads combined with a network partition or a slow/dead Redis; closing the client before draining application-level consumers.
Related errors
- redis: connection pool timeout
- redis: timed out trying to mark connection as unusable
- relaxed timeout must be greater than 0
- post-handoff relaxed duration must be greater than or equal
- shutdown
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/df552659d5e2d4a5.json.
Report an issue: GitHub.