Skip to content

AVRO-4048: [java] Handle InputStream.skip() robustly in binary decoders - #3967

Open
iemejia wants to merge 1 commit into
apache:mainfrom
iemejia:AVRO-4048-robust-inputstream-skip
Open

AVRO-4048: [java] Handle InputStream.skip() robustly in binary decoders#3967
iemejia wants to merge 1 commit into
apache:mainfrom
iemejia:AVRO-4048-robust-inputstream-skip

Conversation

@iemejia

@iemejia iemejia commented Aug 24, 2026

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

The binary decoders mishandled InputStream.skip(). Per its contract, skip() may return 0 without being at end of stream, and negative returns are not specified. This PR fixes three related problems:

  1. Spurious EOF (the reported issue). BinaryDecoder.InputStreamByteSource.skipSourceBytes/trySkipBytes treated two consecutive 0 returns as EOF (EOFException), so a stream that legitimately returns 0 from skip() could fail spuriously. It also carried a dead "negative return" branch (AVRO-4048).

  2. Over-skip / stream corruption. trySkipBytes requested the original full length on every iteration (in.skip(length)) instead of the remaining amount (in.skip(leftToSkip)). On a partial skip this re-requests too much and can advance the underlying stream past the intended position, returning a skipped-count larger than requested.

  3. DirectBinaryDecoder.doSkipBytes treated skip() <= 0 as immediate EOF, sharing the same spurious-EOF problem.

How was this patch fixed?

Replace the fragile "two zeros = EOF" / negative-branch heuristics with a single-byte read() probe: when skip() returns a non-positive value, read one byte to tell a genuine EOF (read() == -1) apart from a transient inability to skip, then continue. This removes the dead negative branch, eliminates the spurious EOF, and cannot infinite-loop. trySkipBytes now skips only the remaining count (leftToSkip). The same probe is applied to DirectBinaryDecoder.doSkipBytes.

How was this patch tested?

New TestBinaryDecoderSkip:

  • bufferedSkipFixedWithZeroSkipStream / directSkipFixedWithZeroSkipStream — a stream whose skip() always returns 0 no longer causes a spurious EOFException; the decoder is positioned exactly after the skipped bytes.
  • bufferedSkipFixedPastEndThrows / directSkipFixedPastEndThrows — skipping past the real end still raises EOFException.
  • inputStreamSkipDoesNotOverSkip — a partial-skip stream is skipped by exactly the requested count (no over-skip).

Verified the tests fail against the current code (over-skip returns 6 instead of 5; the zero-skip cases throw EOFException) and pass with the fix. No regressions across TestBinaryDecoder (68), TestValidatingIO (972), TestBlockingIO (376), and TestDataFile, under the default, custom-coders, and without-fast-reader surefire profiles.

The binary decoders mishandled InputStream.skip() returning 0. Per its
contract, skip() may return 0 without being at end of stream, and negative
returns are not specified. The previous code treated two consecutive 0
returns (and any negative return) as EOF, which could raise a spurious
EOFException on streams that legitimately return 0 from skip(). It also
carried a dead "negative return" branch.

Additionally, BinaryDecoder.InputStreamByteSource.trySkipBytes requested the
original full length on every iteration (in.skip(length)) instead of the
remaining amount (in.skip(leftToSkip)). On a partial skip this re-requested
too much and could advance the stream past the intended position
(over-skip / stream corruption).

Replace the fragile heuristics with a single-byte read() probe: when skip()
returns a non-positive value, read one byte to distinguish a genuine EOF
(read() == -1) from a transient inability to skip, then continue. Fix
trySkipBytes to skip only the remaining count. The same read()-probe logic
is applied to DirectBinaryDecoder.doSkipBytes, which previously treated
skip() == 0 as immediate EOF.

Adds TestBinaryDecoderSkip covering: no spurious EOF on a zero-skip stream
(buffered and direct), genuine EOF still detected when skipping past the
end, and no over-skip on partial-skip streams.
@github-actions github-actions Bot added the Java Pull Requests for Java binding label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Java Pull Requests for Java binding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant