go-redis/redis · critical

redis: failed to create pubsub pool: %w

Error message

redis: failed to create pubsub pool: %w

What it means

Panicked from NewClient when newPubSubPool(opt, dialHook, name) returns an error after the main pool was already created. The pub/sub pool is dedicated to PubSub connections; failure here is fatal and the client panics rather than returning a structurally inconsistent client.

Source

Thrown at redis.go:1958

	// Use void processor for RESP2 connections (push notifications not available)
	c.pushProcessor = initializePushProcessor(opt)
	// set opt push processor for child clients
	c.opt.PushNotificationProcessor = c.pushProcessor

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

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

	// Optionally create a separate connection pool for pipelining, with its own
	// (typically larger) buffers, so pipelines can use big buffers without
	// bloating the main pool. Enabled when either pipeline buffer size is set.
	if opt.PipelineReadBufferSize > 0 || opt.PipelineWriteBufferSize > 0 {
		pipelineOpt := opt.clone()
		if opt.PipelineReadBufferSize > 0 {
			pipelineOpt.ReadBufferSize = opt.PipelineReadBufferSize
			// Same clamp Options.init applies to the main pool: RESP3 push
			// parsing needs a minimum read buffer, and a tiny pipeline reader
			// would break push-notification handling on pipeline conns.
			if pipelineOpt.Protocol == 3 && pipelineOpt.ReadBufferSize < proto.MinRESP3ReadBufferSize {
				pipelineOpt.ReadBufferSize = proto.MinRESP3ReadBufferSize
			}
		}
		if opt.PipelineWriteBufferSize > 0 {
			pipelineOpt.WriteBufferSize = opt.PipelineWriteBufferSize

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Wrap NewClient in recover() to surface the panic as an error.
  2. Validate shared Options (PoolSize, PoolTimeout, MinIdleConns) before construction.
  3. Inspect the wrapped error for the pubsub-pool-specific rejection.
  4. If reproducible, bisect Options to find which field triggers the pool constructor error.

Example fix

// before
client := redis.NewClient(opt)

// after
var client *redis.Client
func() {
    defer func() {
        if r := recover(); r != nil {
            err, _ = r.(error)
        }
    }()
    client = redis.NewClient(opt)
}()
Defensive patterns

Strategy: try-catch

Validate before calling

// Same pool sizing validation as the main pool applies to the pubsub pool.
if opt.PoolSize <= 0 || opt.PoolTimeout <= 0 {
    return errors.New("invalid pool options for pubsub pool")
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        return nil, fmt.Errorf("redis NewClient (pubsub pool) failed: %v", r)
    }
}()
return redis.NewClient(opt), nil

Prevention

When it happens

Trigger: redis.NewClient(opt) where the pubsub pool constructor fails. Same class as the main pool panic but for the pubsub-specific pool, which can be governed by different sizing options if customization is applied.

Common situations: Same shape as the main pool panic: invalid sizing, a bad hook, or an internal pool validation failure. Less common because the pubsub pool reuses the same opt, but a custom PubSub-specific configuration path or future option can trigger it.

Related errors


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