redis/go-redis · error

csc: a different "invalidate" push handler is already regist

Error message

csc: a different "invalidate" push handler is already registered

What it means

go-redis's built-in client-side caching registers a RESP3 'invalidate' push handler on the connection. Only one handler can be bound, and it must be bound to the same cache; errInvalidateHandlerBound (returned by bindTo/registerInvalidateHandler) is returned when a second cache tries to piggyback on a handler already bound to a different live cache, because the new cache would never be invalidated.

Source

Thrown at csc_integration.go:156

	return typ == reflect.TypeOf(b) && typ.Comparable() && a == b
}

func isNilCache(cache Cache) bool {
	if cache == nil {
		return true
	}
	v := reflect.ValueOf(cache)
	switch v.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
		return v.IsNil()
	default:
		return false
	}
}

// errInvalidateHandlerBound: piggybacking on a handler bound to a live
// different cache would leave the new cache uninvalidated.
var errInvalidateHandlerBound = errors.New(`csc: a different "invalidate" push handler is already registered`)

// bindTo binds the handler to (cache, keyPrefix). Success when that is already the
// binding (a derived Client.Conn sharing the parent's processor and cache) or
// when the handler was released by a previous owner's teardown (rebind);
// errInvalidateHandlerBound otherwise.
func (h *invalidateHandler) bindTo(cache Cache, keyPrefix string) error {
	h.mu.Lock()
	defer h.mu.Unlock()
	switch {
	case sameCache(h.cache, cache) && h.keyPrefix == keyPrefix:
		h.users++
		return nil
	case h.cache == nil:
		h.cache, h.keyPrefix = cache, keyPrefix
		h.users = 1
		return nil
	default:
		return errInvalidateHandlerBound

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Use one cache per shared client/connection, or create a separate redis.Client (own connection) for each cache.
  2. Ensure the previous cache is torn down/released before binding a new one to the same connection.
  3. If caches genuinely share the same cache instance and keyPrefix, bindTo succeeds idempotently — verify you are not accidentally constructing a second cache object.

Example fix

// before
cache1 := redis.NewClientCache(client, ...)
cache2 := redis.NewClientCache(client, ...) // same conn, different cache -> error
// after
cache1 := redis.NewClientCache(client, ...)
client2 := redis.NewClient(clientOpts) // separate connection
cache2 := redis.NewClientCache(client2, ...)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure only one cache binds per connection before creating caches
// (track bindings in a registry keyed by client)
if alreadyBound(client) && cacheFor(client) != newCache {
    return errInvalidateHandlerBound
}

Try / catch

if err := cache.bindTo(c, prefix); err != nil {
    if errors.Is(err, errInvalidateHandlerBound) {
        // use a dedicated client/connection for this cache
    }
}

Prevention

When it happens

Trigger: Creating two caches with built-in CSC sharing one underlying connection/processor and binding both; deriving a second, different cache from a shared parent client's Conn (Client.Conn()) without releasing the first.

Common situations: Sharing a single redis.Client across multiple application caches; leaking a first cache without teardown then creating another on the same connection.

Related errors


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