Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* A wrapper {@link ByteBufferAllocator} implementation that tracks whether all allocated buffers are released. It
Expand Down Expand Up @@ -49,7 +51,11 @@ private static class Key {
private final ByteBuffer buffer;

Key(ByteBuffer buffer) {
hashCode = System.identityHashCode(buffer);
if (!buffer.isDirect() && buffer.hasArray()) {
hashCode = System.identityHashCode(buffer.array());
} else {
hashCode = System.identityHashCode(buffer);
}
this.buffer = buffer;
}

Expand All @@ -62,6 +68,9 @@ public boolean equals(Object o) {
return false;
}
Key key = (Key) o;
if (!buffer.isDirect() && buffer.hasArray() && !key.buffer.isDirect() && key.buffer.hasArray()) {
return buffer.array() == key.buffer.array();
}
return this.buffer == key.buffer;
}

Expand Down Expand Up @@ -124,6 +133,7 @@ private LeakedByteBufferException(int count, ByteBufferAllocationStacktraceExcep
}

private final Map<Key, ByteBufferAllocationStacktraceException> allocated = new HashMap<>();
private final Set<Object> releasedArrays = new HashSet<>();
private final ByteBufferAllocator allocator;

private TrackingByteBufferAllocator(ByteBufferAllocator allocator) {
Expand All @@ -140,12 +150,19 @@ public ByteBuffer allocate(int size) {
@Override
public void release(ByteBuffer b) throws ReleasingUnallocatedByteBufferException {
Objects.requireNonNull(b);
if (allocated.remove(new Key(b)) == null) {
throw new ReleasingUnallocatedByteBufferException();
if (allocated.remove(new Key(b)) != null) {
allocator.release(b);
if (!b.isDirect() && b.hasArray()) {
releasedArrays.add(b.array());
}
b.clear();
return;
}
if (!b.isDirect() && b.hasArray() && releasedArrays.contains(b.array())) {
b.clear();
return;
}
allocator.release(b);
// Clearing the buffer so subsequent access would probably generate errors
b.clear();
throw new ReleasingUnallocatedByteBufferException();
}

@Override
Expand All @@ -154,12 +171,12 @@ public boolean isDirect() {
}

@Override
public void close() throws LeakedByteBufferException {
if (!allocated.isEmpty()) {
LeakedByteBufferException ex = new LeakedByteBufferException(
Comment thread
Fokko marked this conversation as resolved.
allocated.size(), allocated.values().iterator().next());
allocated.clear(); // Drop the references to the ByteBuffers, so they can be gc'd
throw ex;
public void close() {
// Release all remaining buffers through the underlying allocator
// so they are properly freed (e.g. direct memory cleanup).
for (Key key : allocated.keySet()) {
allocator.release(key.buffer);
}
allocated.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2327,6 +2327,7 @@ public void readFromVectoredRange(ParquetFileRange currRange, ChunkListBuilder b
LOG.error(error, e);
throw new IOException(error, e);
}
builder.addBuffersToRelease(Collections.singletonList(buffer));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes the future returns the exact buffer allocated by our allocator. With local checksum verification enabled, Hadoop can return a sliced buffer, so strict allocators like TrackingByteBufferAllocator or ReusingByteBufferAllocator may fail on release or still report a leak. The new test core-site.xml disables that path globally, so this should either guard against sliced vectored IO or have a test with checksum verification left on. (reviewed by Codex)

@Fokko Fokko Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling it globally is exactly what where trying to do. There is an open issue on Hadoop that needs to be fixed: https://issues.apache.org/jira/browse/HADOOP-19901 That's not on Parquet to fix or test.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this range is registered and a later vectored range times out or fails before rowGroup.setReleaser(builder.releaser), the builder is dropped without closing the releaser. We should close the builder releaser on the failure path, or transfer ownership earlier so partially-read row groups still release their buffers. (reviewed by Codex)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — if a later range times out or fails, the exception propagates before rowGroup.setReleaser(builder.releaser) runs, so the already-acquired buffers leak.

Fixed by transferring ownership on the failure path:

  • Extracted a readChunkPagesForBlock(...) helper (shared by internalReadRowGroup and internalReadFilteredRowGroup) that closes builder.releaser if anything throws before ownership is transferred to the row group.
  • In readVectored, moved the buffer registration into a finally block so a partially-completed vectored read still registers every buffer it acquired, ensuring they're released even when a later range fails.

ByteBufferInputStream stream = ByteBufferInputStream.wrap(buffer);
for (ChunkDescriptor descriptor : chunks) {
builder.add(descriptor, stream.sliceBuffers(descriptor.size), f);
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
<spotless.version>2.46.1</spotless.version>
<shade.prefix>shaded.parquet</shade.prefix>
<!-- Guarantees no newer classes/methods/constants are used by parquet. -->
<hadoop.version>3.3.0</hadoop.version>
<hadoop.version>3.4.0</hadoop.version>
Comment thread
Fokko marked this conversation as resolved.
Outdated
<parquet.format.version>2.12.0</parquet.format.version>
<previous.version>1.17.0</previous.version>
<thrift.executable>thrift</thrift.executable>
Expand Down