apache/druid · warning

Failed to call cleaner of class

Error message

Failed to call cleaner of class[%s]

What it means

SettableAsyncResource.close() invokes a deferred cleaner (a Closeable created at construction) via CloseableUtils.closeAndSuppressExceptions, and logs a warning if that cleaner's close() throws. The resource is still marked CLOSED; the exception is suppressed because close failures of the cleaner are non-fatal, typically meaning some underlying buffer/file was already released or the cleaner failed to run.

Solutions

  1. Inspect the nested warning for the cleaner's class name and root exception (usually AlreadyClosedException or IOException)
  2. Avoid closing the same SettableAsyncResource from multiple code paths; centralize ownership of close()
  3. Ensure shutdown ordering closes dependent allocators/byte-buffers after all SettableAsyncResource instances
  4. If the warning is noisy but harmless (double-close pattern), guard close() with the resource's state check before calling

Example fix

// before
resource.close(); // called in two paths, second close trips cleaner
// after
if (!resource.isClosed()) {
  resource.close();
}
Defensive patterns

Strategy: try-catch

Try / catch

// Suppress-and-log pattern mirrors the library
try {
  resource.close();
} catch (Exception e) {
  log.warn(e, "Ignoring close failure of async resource");
}

Prevention

When it happens

Trigger: Calling SettableAsyncResource.close() when the deferred cleaner's close() throws, e.g. the cleaner tries to free a native buffer, file handle, or off-heap resource and hits an IOException or IllegalStateException because the resource was already released elsewhere.

Common situations: Double-close of a resource (both a query-cancellation path and normal close call close()); cleaner referencing off-heap memory already freed by the JVM/other shutdown hook; shutdown sequence where the underlying allocator is closed before the async resource.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/9f5ed028fbf2aad4. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/common/asyncresource/SettableAsyncResource.java:274

    final Closeable deferredCloseable;

    synchronized (this) {
      deferredCloseable = switch (state) {
        case NEW -> canceler != null ? canceler::run : null;
        case READY -> result;
        case RELEASED -> null;
        default -> throw DruidException.defensive("Already closed");
      };

      // Clear result and canceler to allow GC.
      result = null;
      canceler = null;
      state = State.CLOSED;
    }

    CloseableUtils.closeAndSuppressExceptions(
        deferredCloseable,
        e -> LOG.warn(e, "Failed to call cleaner of class[%s]", deferredCloseable.getClass())
    );
  }

  @GuardedBy("this")
  private List<Runnable> drainCallbacks()
  {
    final List<Runnable> snapshot = List.copyOf(readyCallbacks);
    readyCallbacks.clear();
    return snapshot;
  }

  private boolean setInternal(Either<Throwable, ResourceHolder<T>> value)
  {
    final boolean didSet;
    final List<Runnable> callbacksToFire;

    synchronized (this) {
      didSet = switch (state) {

View on GitHub (pinned to 9b90983fd2)