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
- Inspect the nested warning for the cleaner's class name and root exception (usually AlreadyClosedException or IOException)
- Avoid closing the same SettableAsyncResource from multiple code paths; centralize ownership of close()
- Ensure shutdown ordering closes dependent allocators/byte-buffers after all SettableAsyncResource instances
- 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
- Close each SettableAsyncResource exactly once from a single owner
- Use try-with-resources or a finally block so close ordering is deterministic
- Close dependent allocators after async resources, never before
- Monitor warnings for repeated AlreadyClosedException, which signals double-close bugs
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)