apache/druid · error · IllegalStateException

cache [%s] is already attached

Error message

cache [%s] is already attached

What it means

OnHeapNamespaceExtractionCacheManager tracks materialized caches as WeakReferences in a caches collection. attachCache() refuses to attach a CacheHandler whose id is already tracked, throwing this ISE to prevent the same cache being registered twice, which would break disposal accounting and leak tracking.

Source

Thrown at extensions-core/lookups-cached-global/src/main/java/org/apache/druid/server/lookup/namespace/cache/OnHeapNamespaceExtractionCacheManager.java:107

    expungeCollectedCaches();
    caches.add(cacheRef);
    return new CacheHandler(this, cache, cacheRef);
  }

  @Override
  public CacheHandler allocateCache()
  {
    // Object2ObjectOpenHashMap has a bit smaller footprint than HashMap
    Map<String, String> cache = new Object2ObjectOpenHashMap<>();
    // untracked, but disposing will explode if we don't create a weak reference here
    return new CacheHandler(this, cache, new WeakReference<>(cache));
  }

  @Override
  public CacheHandler attachCache(CacheHandler cache)
  {
    if (caches.contains((WeakReference<Map<String, String>>) cache.id)) {
      throw new ISE("cache [%s] is already attached", cache.id);
    }
    // replace Object2ObjectOpenHashMap with ImmutableLookupMap
    final ImmutableLookupMap immutable = ImmutableLookupMap.fromMap(cache.getCache());
    WeakReference<Map<String, String>> cacheRef = new WeakReference<>(immutable);
    expungeCollectedCaches();
    caches.add(cacheRef);
    return new CacheHandler(this, immutable, cacheRef);
  }

  @Override
  public LookupExtractor asLookupExtractor(
      final CacheHandler cache,
      final boolean isOneToOne,
      final Supplier<byte[]> cacheKeySupplier
  )
  {
    if (cache.getCache() instanceof ImmutableLookupMap) {
      return ((ImmutableLookupMap) cache.getCache()).asLookupExtractor(isOneToOne, cacheKeySupplier);

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Attach each CacheHandler exactly once; let the CacheScheduler manage attachment.
  2. Check/track attachment state before calling attachCache, or remove the stale reference first.
  3. Serialize cache creation for a namespace to avoid double-attach races.

Example fix

// before
manager.attachCache(cache);
manager.attachCache(cache); // ISE: already attached
// after
if (!isAttached(cache)) {
  manager.attachCache(cache);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// attach only once per namespace entry
Set<Object> attached = new HashSet<>();
if (!attached.add(cache.id)) { /* skip duplicate attach */ }

Try / catch

try { manager.attachCache(cache); } catch (IllegalStateException e) { if (e.getMessage().contains("is already attached")) { /* ignore: already managed */ } else { throw e; } }

Prevention

When it happens

Trigger: Calling attachCache(cache) with a CacheHandler already attached (double scheduling/attachment of the same namespace entry, or re-entrant cache creation for the same id).

Common situations: Race between two cache-update paths for the same namespace; calling attachCache manually in tests on a handler already managed by the scheduler; reusing a CacheHandler after a failed detach.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/d4986f43a57684a8. Report an issue: GitHub.