redis/go-redis · critical

redis: failed to create connection pool: %w

Error message

redis: failed to create connection pool: %w

What it means

NewFailoverClient dials nothing itself, but it does build the local connection pool. If pool construction (newConnPool) fails, the client panics, wrapping the underlying error with this message. Because pools allocate buffers/resources eagerly, failure here usually indicates invalid pool configuration rather than a network problem.

Source

Thrown at sentinel.go:593

			onClose:  &onCloseHooks{},
			himport:  newHImportRegistry(),
		},
	}
	rdb.init()

	// Initialize push notification processor using shared helper
	// Use void processor by default for RESP2 connections
	rdb.pushProcessor = initializePushProcessor(opt)

	// Generate unique pool names for metrics
	uniqueID := generateUniqueID()
	mainPoolName := opt.Addr + "_" + uniqueID
	pubsubPoolName := opt.Addr + "_" + uniqueID + "_pubsub"

	var err error
	rdb.connPool, err = newConnPool(opt, rdb.dialHook, mainPoolName)
	if err != nil {
		panic(fmt.Errorf("redis: failed to create connection pool: %w", err))
	}
	rdb.pubSubPool, err = newPubSubPool(opt, rdb.dialHook, pubsubPoolName)
	if err != nil {
		panic(fmt.Errorf("redis: failed to create pubsub pool: %w", err))
	}

	// Create the dedicated pipeline pool unconditionally, mirroring NewClient
	// via the shared buildPipelinePool helper. PipelinePoolSize < 0 opts out.
	if opt.PipelinePoolSize >= 0 {
		ref, err := rdb.buildPipelinePool(mainPoolName + "_pipeline")
		if err != nil {
			panic(fmt.Errorf("redis: failed to create pipeline connection pool: %w", err))
		}
		rdb.pipelinePool = ref
	}

	// Register pools for OTel async gauge metrics, matching NewClient (the
	// failover client previously registered none, so pool gauges were silent

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Recover the panic and inspect the wrapped error (%w) to find the root cause
  2. Validate FailoverOptions before construction (Dialer set, sane PoolSize/MinIdleConns)
  3. If constructing via URL, run ParseFailoverURL first so invalid values are rejected as errors instead of panics

Example fix

// before
rdb := redis.NewFailoverClient(opt) // panics on bad pool config
// after
if opt.Dialer == nil && opt.Addr == "" { return fmt.Errorf("missing dialer") }
rdb := redis.NewFailoverClient(opt)
Defensive patterns

Strategy: try-catch

Validate before calling

if opt == nil { return errors.New("nil FailoverOptions") }
if opt.Dialer == nil && len(opt.SentinelAddrs) == 0 { return errors.New("no dialer or sentinel addrs") }

Try / catch

func newFailoverSafe(opt *redis.FailoverOptions) (rdb *redis.Client, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("NewFailoverClient: %v", r)
        }
    }()
    rdb = redis.NewFailoverClient(opt)
    return rdb, nil
}

Prevention

When it happens

Trigger: Calling NewFailoverClient (or NewUniversalClient with sentinel options) with options that make newConnPool fail — e.g. an invalid Dialer, nil/broken Dialer with custom settings, or hook/pool configuration that errors at setup.

Common situations: Programmatic construction of FailoverOptions with fields that bypass URL validation, misconfigured custom dialers in tests, or options produced by merging configs where the pool setup becomes invalid.

Related errors


AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01). Data as JSON: /api/errors/f3b49aa61b616468. Report an issue: GitHub.