{"record":{"id":"de5e3256699f3713","repo":"apache/druid","slug":"already-closed-de5e32","errorCode":null,"errorMessage":"Already closed!","messagePattern":"Already closed!","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/collections/ReferenceCountingResourceHolder.java","lineNumber":74,"sourceCode":"    this.object = object;\n    this.closer = closer;\n    this.cleanable = Cleaners.register(this, new CloserRunnable(object, closer, refCount));\n  }\n\n  public static <T extends Closeable> ReferenceCountingResourceHolder<T> fromCloseable(final T object)\n  {\n    return new ReferenceCountingResourceHolder<>(object, object);\n  }\n\n  /**\n   * Returns the resource with an initial reference count of 1. More references can be added by\n   * calling {@link #increment()}.\n   */\n  @Override\n  public T get()\n  {\n    if (refCount.get() <= 0) {\n      throw new ISE(\"Already closed!\");\n    }\n    return object;\n  }\n\n  /**\n   * Increments the reference count by 1 and returns a {@link ResourceHolder} representing the new references.\n   * The returned {@link ResourceHolder} \"close\" method decrements the reference count when the caller no longer\n   * needs the resource.\n   *\n   * Returned {@link ResourceHolder} are not thread-safe. If multiple threads need references to the same resource, they\n   * should each call this method on the original object.\n   */\n  public ResourceHolder<T> increment()\n  {\n    while (true) {\n      int count = this.refCount.get();\n      if (count <= 0) {\n        throw new ISE(\"Already closed!\");","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/collections/ReferenceCountingResourceHolder.java#L56-L92","documentation":"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.","triggerScenarios":"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).","commonSituations":"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().","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"],"exampleFix":"// before\nholder.close();\nT obj = holder.get(); // ISE: Already closed!\n// after\ntry (ResourceHolder<T> extended = holder.increment()) {\n  use(extended.get());\n}\nholder.close();","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { T obj = holder.get(); } catch (IllegalStateException e) { /* holder already released: reacquire resource */ }","preventionTips":["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"],"tags":["java","resource-lifecycle","use-after-close"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}