redis/go-redis · critical

redis: failed to create pubsub pool: %w

Error message

redis: failed to create pubsub pool: %w

What it means

NewFailoverClient also creates a dedicated Pub/Sub connection pool; failure in newPubSubPool causes a panic wrapping the underlying error with this message. Like the main pool error, this points at invalid pool options rather than a live network failure, since pool construction is local setup.

Source

Thrown at sentinel.go:597

	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
	// for the identical standalone setup). The pipeline pool is nil when not
	// configured.
	otel.RegisterPools(rdb.connPool, rdb.pubSubPool, rdb.getPipelinePool(), opt.Addr)

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Recover and inspect the wrapped error for the root cause
  2. Verify the same FailoverOptions work with a plain NewClient to isolate sentinel-specific handling
  3. Fix the offending option (Dialer, buffer sizes, PoolSize) before constructing the client

Example fix

// before
rdb := redis.NewFailoverClient(opt) // panics: failed to create pubsub pool
// after
if opt.ReadBufferSize <= 0 { opt.ReadBufferSize = 32 * 1024 }
rdb := redis.NewFailoverClient(opt)
Defensive patterns

Strategy: try-catch

Validate before calling

if opt == nil { return errors.New("nil FailoverOptions") }
if opt.PoolSize < 0 { return fmt.Errorf("invalid PoolSize %d", opt.PoolSize) }

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: NewFailoverClient / NewUniversalClient with sentinel options where newPubSubPool fails — typically the same invalid dialer or pool-option conditions as the main-pool failure, but surfacing in the pubsub pool step.

Common situations: Shared options struct reused across clients after one field was mutated to an invalid value, custom hooks that break pool setup, or memory/resource constraints during eager buffer allocation.

Related errors


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