Skip to content

GH-1464: Clear vectors before loading their buffers - #1465

Merged
jbonofre merged 2 commits into
apache:mainfrom
jbonofre:gh-followup-load-validity
Nov 13, 2026
Merged

jbonofre merged 2 commits into
apache:mainfrom
jbonofre:gh-followup-load-validity

Conversation

@jbonofre

@jbonofre jbonofre commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

What's Changed

Found while reviewing #1422. This change is independent of it and based on main.

Clear a vector before loading its buffers. Every loadFieldBuffers released the vector's current buffers one at a time, each just before loading its replacement. If one of those loads failed (for example OutOfMemoryException while creating the validity buffer, or IllegalStateException while retaining a buffer from another root allocator), the vector kept pointing at a released buffer, and closing it failed with RefCnt has gone negative. The vector's own buffers are now released and replaced with empty buffers, and its value count is reset, before anything is loaded. This applies to BaseFixedWidthVector, BaseVariableWidthVector, BaseLargeVariableWidthVector, ListVector, LargeListVector, ListViewVector, LargeListViewVector, FixedSizeListVector and StructVector, so a failed load leaves the vector empty but usable. The leaf vectors call clear(), as BaseVariableWidthViewVector already did. The list, list view, fixed size list and struct vectors release only their own buffers, so their child vectors are kept.

Handle a missing validity buffer in BitVectorHelper.loadValidityBuffer.

  • With the unknown null count -1, a missing (NULL or empty) validity buffer is now read as all valid, like null count 0. Before, it was kept as is: a NullPointerException for a NULL C Data buffer, or an empty validity buffer for IPC that isNull(i) then read past.
  • A missing validity buffer is now rejected with IllegalArgumentException when the null count says that only some values are null, instead of being kept.

Tests: TestLoadFieldBuffers covers all ten vector types with a failed validity load, and the eight with more than one buffer with a failed buffer retain. Each time, the vector is left with a value count of 0 and closes with no double release or leak. TestBitVectorHelper covers the two loadValidityBuffer cases. The full vector test suite passes.

Closes #1464.

loadFieldBuffers released the vector's validity buffer before
BitVectorHelper.loadValidityBuffer created the new one. When that failed,
for example on an allocation failure, the vector still pointed at the
released buffer, and closing it later released the buffer again. Create
the new validity buffer first in every vector that loads one.

loadValidityBuffer also treated a missing validity buffer with an unknown
null count (-1) as the mixed case, and kept the null or empty source
buffer: a NullPointerException for a NULL C Data buffer, or a vector whose
validity buffer is too small. Treat it as all valid, and reject a missing
validity buffer when the null count says that only some values are null.
@jbonofre jbonofre added the bug-fix PRs that fix a big. label Nov 12, 2026
@github-actions github-actions Bot added this to the 20.0.0 milestone Nov 12, 2026
validityBuffer = BitVectorHelper.loadValidityBuffer(fieldNode, bitBuffer, allocator);
validityBuffer = newValidityBuffer;
valueBuffer.getReferenceManager().release();
valueBuffer = dataBuffer.getReferenceManager().retain(dataBuffer, allocator);

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.

Shouldn't we be doing the same thing for the value buffer? Otherwise you still have an inconsistent vector.

Also, this would mean that you temporarily have 2x memory usage instead of 1x as before. Is it necessarily wrong to say that failing a load means the vector is cleared? After all, if you're loading data, presumably you didn't care about the previous contents of the vector anymore.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good points, thanks. You're right: the offset, size and value buffers had the same problem (retain can still throw, for example for a buffer from another root allocator), and there's no reason to keep the old contents of a vector we're loading into.

I changed loadFieldBuffers to release the vector's own buffers and reset its value count before loading anything. A failed load now leaves the vector empty but closeable, with no extra allocation. The leaf vectors just call clear(), as BaseVariableWidthViewVector already did. The list and struct vectors only release their own buffers, so their child vectors are left alone.

I also added a test where retaining a buffer after the validity buffer fails. On the previous commit it hit RefCnt has gone negative on close for every vector except BaseVariableWidthViewVector, which instead leaked the new validity buffer, so I reverted that one to main.

Instead of loading the new validity buffer before releasing the old one,
release the vector's own buffers first and reset its value count, then
load. A failure while loading the validity buffer, or while retaining any
other buffer, now leaves the vector empty but usable, without the extra
memory of keeping the old validity buffer. List, list view, fixed size list
and struct vectors keep their child vectors.

BaseVariableWidthViewVector already cleared itself first: restore it, since
loading its validity buffer before clear() leaked that buffer when
retaining the view buffer failed.
@jbonofre jbonofre changed the title GH-1464: Keep vectors usable when loading their validity buffer fails GH-1464: Clear vectors before loading their buffers Nov 13, 2026
@jbonofre
jbonofre requested a review from lidavidm November 13, 2026 05:29
// Release this vector's buffers first, so that a failure while loading leaves it empty but
// usable. Unlike clear(), this keeps the child vectors.
validityBuffer = releaseBuffer(validityBuffer);
offsetBuffer = releaseBuffer(offsetBuffer);

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.

I guess this runs into the question of whether an empty offset buffer is valid for a list vector...but it's consistent with before so we can defer that discussion.

@jbonofre
jbonofre merged commit 6957bdc into apache:main Nov 13, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PRs that fix a big.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Java] Vectors are left with a released validity buffer when loading their buffers fails

2 participants