apache/druid · critical · RuntimeException
Leaks happened, each suppressed exception represents one…
Error message
Leaks happened, each suppressed exception represents one code path that checked out an object and didn't return it.
What it means
StupidPool.take throws this when the pool has been 'poisoned' by a previously detected leak (a checked-out object's holder was garbage-collected without close/return) and the recorded leak list is present. Each suppressed LeakedException carries the stack trace of a code path that checked out an object (typically an off-heap ByteBuffer) and never returned it. This is a resource-leak detection mechanism, not a transient failure.
Solutions
- Inspect the suppressed LeakedException stack traces to identify the code path that leaked the object.
- Ensure every ResourceHolder from take() is used in try-with-resources or explicitly closed in a finally block.
- Fix the leaky code path (Druid or extension code) that dropped the holder without returning it.
- Restart/retry after the leak fix — the pool stays poisoned, so the error repeats until the process is restarted.
Example fix
// before
ResourceHolder<ByteBuffer> holder = pool.take();
ByteBuffer buf = holder.get();
process(buf); // may throw; holder never closed
// after
try (ResourceHolder<ByteBuffer> holder = pool.take()) {
process(holder.get());
} Defensive patterns
Strategy: try-catch
Validate before calling
if (pool.poolSize() < 0 || pool.leakedObjectsCount() > 0) { log.warn("pool has prior leaks; holders may not be closed somewhere"); } Try / catch
try (ResourceHolder<T> holder = pool.take()) {
use(holder.get());
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Leaks happened")) {
for (Throwable suppressed : e.getSuppressed()) { logLeakSite(suppressed); }
}
throw e;
} Prevention
- Always use try-with-resources for ResourceHolder objects taken from StupidPool.
- Never let exceptions escape between take() and close().
- Enable leak-detection early in tests so leak sites surface before production.
- Restart the process to clear the poisoned pool only after identifying the leak.
When it happens
Trigger: Pool is poisoned by a leak notification (Cleaner detected an unclosed ObjectResourceHolder); a later take() finds no pooled object and capturedException is non-null, so makeExceptionForLeaks throws with all leak stack traces as suppressed exceptions.
Common situations: Query processing code paths that allocate merge buffers/column caches and exit early on exceptions without closing holders; forgotten close() in custom operator or extension code; holder dropped without try-with-resources.
Related errors
- Bad null-marker byte, delegate class
- Cannot return double for Null Value
- Cannot return float for Null Value
- Cannot return long for Null Value
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/6f592e2d17cf2f35.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/collections/StupidPool.java:154
}
@Override
public String toString()
{
return "StupidPool{" +
"name=" + name +
", objectsCacheMaxCount=" + objectsCacheMaxCount +
", poolSize=" + poolSize() +
"}";
}
@Override
public ResourceHolder<T> take()
{
ObjectResourceHolder resourceHolder = objects.poll();
if (resourceHolder == null) {
if (POISONED.get() && capturedException.get() != null) {
throw makeExceptionForLeaks(capturedException.get());
}
return makeObjectWithHandler();
} else {
poolSize.decrementAndGet();
if (POISONED.get()) {
final CopyOnWriteArrayList<LeakedException> exceptionList = capturedException.get();
if (exceptionList == null) {
resourceHolder.notifier.except = new LeakedException(Thread.currentThread().getName());
} else {
throw makeExceptionForLeaks(exceptionList);
}
}
return resourceHolder;
}
}
private RuntimeException makeExceptionForLeaks(CopyOnWriteArrayList<LeakedException> exceptionList)
{View on GitHub (pinned to 9b90983fd2)