apache/druid · error · IllegalStateException

Already closed

Error message

Already closed

What it means

DirectByteBufferHolder.close frees the direct ByteBuffer exactly once, guarded by a CAS on `closed`. If close() is invoked a second time, the CAS fails and it throws this IllegalStateException rather than silently double-freeing. It is a defensive check against double-close.

Solutions

  1. Remove redundant close() calls so the holder is closed exactly once, preferring try-with-resources.
  2. Track closed state on your side if the holder may pass through multiple owners.
  3. If double-close may legitimately occur in your code, catch the ISE and treat it as idempotent-close.

Example fix

// before
try (ResourceHolder<ByteBuffer> holder = ByteBufferUtils.allocateDirect(1024)) {
  holder.close(); // double close on scope exit
}
// after
try (ResourceHolder<ByteBuffer> holder = ByteBufferUtils.allocateDirect(1024)) {
  // use holder only
}
Defensive patterns

Strategy: validation

Try / catch

// if idempotent close is desired:
try {
  holder.close();
} catch (IllegalStateException e) {
  // already closed; ignore
}

Prevention

When it happens

Trigger: Calling close() twice on the same DirectByteBufferHolder, e.g. in both a try-with-resources and an explicit finally, or nested resource-management wrappers closing the same holder.

Common situations: Duplicated cleanup code paths (error path plus normal path both closing); wrapping an already-managed holder in another AutoCloseable; refactoring that left an old close call in place.

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/e57cbc75640d5ff8. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/java/util/common/ByteBufferUtils.java:140

      {
        final ByteBuffer theBuf = buf;

        if (theBuf == null) {
          throw new ISE("Closed");
        } else {
          return theBuf;
        }
      }

      @Override
      public void close()
      {
        if (closed.compareAndSet(false, true)) {
          final ByteBuffer theBuf = buf;
          buf = null;
          free(theBuf);
        } else {
          throw new ISE("Already closed");
        }
      }
    }

    return new DirectByteBufferHolder();
  }

  /**
   * Releases memory held by the given direct ByteBuffer
   *
   * @param buffer buffer to free
   */
  public static void free(ByteBuffer buffer)
  {
    if (buffer.isDirect()) {
      clean(buffer);
    }
  }

View on GitHub (pinned to 9b90983fd2)