Skip to content

Bugfix/fix rss sort reader eos full - #12984

Draft
kecookier wants to merge 4 commits into
apache:mainfrom
kecookier:bugfix/fix-rss-sort-reader-eos-full
Draft

Bugfix/fix rss sort reader eos full#12984
kecookier wants to merge 4 commits into
apache:mainfrom
kecookier:bugfix/fix-rss-sort-reader-eos-full

Conversation

@kecookier

@kecookier kecookier commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What changes are proposed in this pull request?

Background

Related issue: #12985
Two bugs were discovered in our internal production environment: shuffle read tasks hit an infinite loop or deserialization failures with Celeborn (rss_sort + hash partitioning).

The regression tests were first submitted in #12983, where the failing cases confirm that both bugs reproduce on current main — hence this PR adds the fix on top.

What

  1. EOS mid-page infinite loop. GlutenByteInputStream::readBytes drives next(true) in a for(;;) loop, but VeloxInputStream::next() ignored the throwIfPastEnd argument and silently returned on EOS — a truncated partition stream made the loop spin forever (task stuck in RUNNABLE). The Read() result was also stored into an unsigned offset_ without a signed guard, so a negative return corrupted setRange.

Fix: next() honors throwIfPastEnd (fails fast on EOS when bytes are still expected), guards the signed Read() result, and the constructor / hasNext() probe with next(false) so a clean EOS on an empty stream stays graceful.

  1. Multi-window page corruption. VeloxInputStream is a single-window refill stream — each next() overwrites the sole ByteRange — but the Presto serde assumes stable multi-range data (tellp → pre-scan → seekp back for nested types, nextView over the payload for checksum verification). When a page spans multiple read windows, the serde's backward seek read window data already overwritten by a refill, corrupting the stream (e.g. Invalid serialized string size).

Fix: VeloxInputStream overrides nextView (refills the window instead of reporting EOS at a range boundary) and tellp/seekp (window-bounded, fail fast). The deserializer gained readPage(): fast path deserializes in-situ when the whole page fits in the current window; slow path reassembles cross-window pages (copied header stitched with the in-situ payload, or a fully copied payload) into a contiguous BufferInputStream so serde seeks never touch refill-overwritten data.

The Presto page wire header is parsed manually (21 bytes, frozen wire format) to avoid depending on velox-internal serde detail headers.

Regression tests in VeloxShuffleReaderTest cover both fixes, driven through the public VeloxRssSortShuffleReaderDeserializer API.

How was this patch tested?

Add unit-test.

Was this patch authored or co-authored using generative AI tooling?

Co-Authored-By: Claude noreply@anthropic.com

Kuo Zhao and others added 4 commits September 9, 2026 15:12
Co-Authored-By: Claude <noreply@anthropic.com>
arrow::io::Readable::Read(int64_t) is pure virtual; without an override
FakeInputStream is abstract and make_shared fails to compile.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…eserialization

Two production issues in the rss_sort shuffle reader:

1. EOS mid-page infinite loop: GlutenByteInputStream::readBytes drives
   next(true) in a for(;;) loop, but VeloxInputStream::next() ignored the
   throwIfPastEnd argument and silently returned on EOS, so a truncated
   partition stream made the loop spin forever (task stuck in RUNNABLE).
   Also, the Read() result was stored into an unsigned offset_ without a
   signed guard, so a negative return corrupted setRange.

   Fix: next() honors throwIfPastEnd (VELOX_FAIL on EOS when bytes are
   still expected), checks the signed Read() result, and the constructor /
   hasNext() probe with next(false) so a clean EOS on an empty stream is
   graceful.

2. Multi-window page corruption: VeloxInputStream is a single-window
   refill stream - each next() overwrites the sole ByteRange, but the
   Presto serde assumes stable multi-range data (tellp -> pre-scan ->
   seekp back for nested types, nextView over the payload for checksum
   verification). When a page spans multiple read windows, the serde's
   backward seek read window data already overwritten by a refill,
   corrupting the stream (e.g. "Invalid serialized string size").

   Fix: VeloxInputStream now overrides nextView (refills the window
   instead of reporting EOS at a range boundary) and tellp/seekp (window
   bounded, fail fast instead of reading overwritten data). The
   deserializer gained readPage(): the fast path deserializes in-situ
   when the whole page fits in the current window; the slow path
   reassembles cross-window pages (copied header stitched with the
   in-situ payload, or a fully copied payload) into a contiguous
   BufferInputStream so serde seeks never touch refill-overwritten data.

The Presto page wire header is parsed manually (21 bytes, frozen wire
format) to avoid depending on velox-internal serde detail headers.

Regression tests in VeloxShuffleReaderTest cover both fixes.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the VELOX label Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant