Skip to content

21671 direct io + readahead exception fix - #5152

Open
rustyrazorblade wants to merge 3 commits into
apache:trunkfrom
rustyrazorblade:21671-readahead-fix
Open

21671 direct io + readahead exception fix#5152
rustyrazorblade wants to merge 3 commits into
apache:trunkfrom
rustyrazorblade:21671-readahead-fix

Conversation

@rustyrazorblade

Copy link
Copy Markdown
Contributor

Fixes direct io + read ahead exception, CASSANRA-21671

Jonathan Haddad added 3 commits September 9, 2026 19:31
…calReadAheadBuffer Block

Block objects are cached in a static thread-local map keyed by file path
and shared across all ThreadLocalReadAheadBuffer instances. bufferSize is a
per-instance field initialised only inside the block.buffer == null branch of
getBlock(). When a second instance, on the same thread and for the same file
path, reused an already-allocated cached Block, that branch was skipped and its
bufferSize stayed -1. fill() then computed Math.min(remaining, -1) == -1 and
called ByteBuffer.limit(-1), throwing IllegalArgumentException: newLimit < 0
and aborting compaction.

Move the bufferSize initialisation out of the block.buffer == null guard so any
instance that observes a Block, fresh or reused, initialises bufferSize from the
buffer capacity.

Add a regression test that reproduces the two-instance, same-path, same-thread
scenario.
Second defect in the same read-ahead Direct IO family as Bug #01. The static
per-thread, per-path Block cache in ThreadLocalReadAheadBuffer is shared across
instances with different buffer ownership:

  - DirectThreadLocalReadAheadBuffer allocates an aligned SLICE via
    BufferUtil.allocateDirectAligned (no Cleaner; attachment = backing
    DirectByteBuffer) and previously overrode cleanBuffer() to clean the
    attachment.
  - Base ThreadLocalReadAheadBuffer allocates an OWNED buffer (has a Cleaner)
    and cleanBuffer() calls MemoryUtil.clean(buffer) directly.

CompressedChunkReader.Direct uses the Direct subclass; .Standard uses the base.
Both can open over the same file path on the same thread and share the same
cached Block. When a Direct instance populates the Block with a slice and a
Standard instance over the same path reuses it (the reuse path Bug #01 exposed),
the Standard instance frees the slice through the base cleanBuffer, calling
MemoryUtil.clean(slice). The hardened MemoryUtil.clean rejects a buffer with no
cleaner and a non-null attachment (a latent double-free pre-hardening), throwing
IllegalArgumentException and aborting BTI compaction.

Make cleanup type-safe in the base class: cleanBuffer() resolves a direct buffer
with no cleaner but a ByteBuffer attachment to its root allocation before calling
MemoryUtil.clean, so any instance frees any buffer correctly. This does not weaken
MemoryUtil.clean's guard; it resolves to the root before calling it. The
DirectThreadLocalReadAheadBuffer.cleanBuffer override is now redundant and is
removed so both paths use the single correct base implementation. The Bug #01
bufferSize fix is unchanged.

Add a regression test that reproduces the Direct-slice/base-free scenario.
@rustyrazorblade rustyrazorblade changed the title 21671 readahead fix 21671 direct io + readahead exception fix Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant