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

  1. Set a finite ReadTimeout (and/or a per-command timeout) so stalled reads cannot outlive Close.
  2. Stop consumer goroutines and wait for them to return BEFORE calling Close(), so no blocking command is still in flight.
  3. Issue blocking commands with an explicit finite BLOCK timeout (e.g. XREAD BLOCK 5000) instead of BLOCK 0.
  4. 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

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


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