apache/druid · error · IllegalStateException
Already closed!
Error message
Already closed!
What it means
ReferenceCountingResourceHolder wraps an object with an AtomicInteger refcount. get() throws IllegalStateException once the count has dropped to zero (the resource was released/closed), preventing use of a resource that may have been returned or reclaimed.
Solutions
- Call increment() to obtain an additional reference BEFORE the original holder can be closed, and get() from the incremented holder
- Restructure so get()/use happens strictly within the lifetime of a live holder (try-with-resources)
- Fix lifecycle ownership: only the owner closes; consumers use increment()-derived holders
- Log/audit close paths to find premature or duplicate close() calls
Example fix
// before
holder.close();
T obj = holder.get(); // ISE: Already closed!
// after
try (ResourceHolder<T> extended = holder.increment()) {
use(extended.get());
}
holder.close(); Defensive patterns
Strategy: try-catch
Try / catch
try { T obj = holder.get(); } catch (IllegalStateException e) { /* holder already released: reacquire resource */ } Prevention
- Call increment() to extend a lease before the original holder may close
- Confine get()/use to the holder's try-with-resources scope
- One owner closes; consumers use increment()-derived holders
When it happens
Trigger: Calling get() after close() decremented the refcount to 0, after increment() failed, or holding a reference beyond the lifetime managed elsewhere (e.g. the underlying object was closed by its owner).
Common situations: Storing a ResourceHolder and using it asynchronously after the owning scope closed it; use-after-close in callbacks/threads; double-close by two owners then a later get().
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
- Already Closed!
- 08001
- A batch appenderator was already created for this peon's…
- A-Not-B requires at least 1 sketch
- A realtime appenderator was already created for this peon's…
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/de5e3256699f3713.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/collections/ReferenceCountingResourceHolder.java:74
this.object = object;
this.closer = closer;
this.cleanable = Cleaners.register(this, new CloserRunnable(object, closer, refCount));
}
public static <T extends Closeable> ReferenceCountingResourceHolder<T> fromCloseable(final T object)
{
return new ReferenceCountingResourceHolder<>(object, object);
}
/**
* Returns the resource with an initial reference count of 1. More references can be added by
* calling {@link #increment()}.
*/
@Override
public T get()
{
if (refCount.get() <= 0) {
throw new ISE("Already closed!");
}
return object;
}
/**
* Increments the reference count by 1 and returns a {@link ResourceHolder} representing the new references.
* The returned {@link ResourceHolder} "close" method decrements the reference count when the caller no longer
* needs the resource.
*
* Returned {@link ResourceHolder} are not thread-safe. If multiple threads need references to the same resource, they
* should each call this method on the original object.
*/
public ResourceHolder<T> increment()
{
while (true) {
int count = this.refCount.get();
if (count <= 0) {
throw new ISE("Already closed!");View on GitHub (pinned to 9b90983fd2)