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
- Attach each CacheHandler exactly once; let the CacheScheduler manage attachment.
- Check/track attachment state before calling attachCache, or remove the stale reference first.
- 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
- Let CacheScheduler own attach/detach; avoid manual attachCache
- Serialize cache creation per namespace to avoid races
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.