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

  1. Call increment() to obtain an additional reference BEFORE the original holder can be closed, and get() from the incremented holder
  2. Restructure so get()/use happens strictly within the lifetime of a live holder (try-with-resources)
  3. Fix lifecycle ownership: only the owner closes; consumers use increment()-derived holders
  4. 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

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


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)