[FLINK-34474][formats] Reset Avro decoder after a failed deserialization - #28932
Open
hulincup wants to merge 3 commits into
Open
[FLINK-34474][formats] Reset Avro decoder after a failed deserialization#28932hulincup wants to merge 3 commits into
hulincup wants to merge 3 commits into
Conversation
AvroDeserializationSchema reuses a pooled MutableByteArrayInputStream and Decoder across messages. On each deserialize it swaps the input buffer via setBuffer, then calls datumReader.read(null, decoder). The JSON path reconfigures the JsonDecoder every call, but the binary path's BinaryDecoder is created once and never reconfigured. When datumReader.read fails mid-record (e.g. a corrupt byte decoding to an out-of-range union tag throws ArrayIndexOutOfBoundsException), the pooled BinaryDecoder keeps unconsumed bytes in its internal buffer. The next message swaps the input buffer, but the decoder's buffer is not cleared, so subsequent reads return corrupted data and every following message fails. Reproduce (reported, binary encoding): valid -> invalid -> valid -> valid becomes VALID -> FAILED -> FAILED -> FAILED. On a failed read, discard the poisoned decoder and rebuild it bound to the current input stream (binaryDecoder reuses the existing decoder per Avro's recommended reuse pattern), then rethrow the original exception. Adds testDeserializeRecoversFromCorruptMessage: a 2-branch union schema where a multi-byte corrupt payload (leading byte 100 -> zig-zag tag 50, out of range) throws mid-read and leaves trailing bytes in the decoder buffer; asserts a valid message still deserializes afterwards.
Collaborator
Spotless requires the assertThatThrownBy chain on a single line.
DecoderFactory.binaryDecoder(InputStream, BinaryDecoder) requires a BinaryDecoder reuse arg, but this.decoder is declared as Decoder (it may also hold a JsonDecoder). Pass null instead to discard the poisoned decoder and build a fresh BinaryDecoder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem fixed & how
AvroDeserializationSchemareuses a pooledMutableByteArrayInputStreamandDecoderacross messages. On eachdeserializeit swaps the input buffer viainputStream.setBuffer(message), then callsdatumReader.read(null, decoder).The JSON path reconfigures the
JsonDecoderevery call (((JsonDecoder) decoder).configure(inputStream)), but the binary path'sBinaryDecoderis created once incheckAvroInitializedand never reconfigured afterwards —setBufferonly swaps the underlying stream, it does not touch the decoder's internal buffer.When
datumReader.readfails mid-record (e.g. a corrupt byte decodes to an out-of-range union tag →ArrayIndexOutOfBoundsException), the pooledBinaryDecoderkeeps unconsumed bytes in its internal buffer. The next message swaps the input buffer, but the decoder's buffer is not cleared, so subsequent reads return corrupted data and every following message fails.Reproducer (reported, binary encoding):
valid → invalid → valid → validbecomesVALID → FAILED → FAILED → FAILED.Behavior modified
deserialize(binary encoding), the pooled decoder stayed poisoned; every subsequent message failed regardless of content.Code refactored
deserializewrapsdatumReader.readin a try-catch; on failure it calls a newresetDecoder()(rebuilds the JSON or binary decoder bound to the current input stream —binaryDecoder(inputStream, this.decoder)follows Avro's recommended reuse pattern) and then rethrows the original exception.Features added / Functions optimized
N/A
Test plan
testDeserializeRecoversFromCorruptMessage(parameterized overBINARYandJSON): a 2-branch union schema (string | int) where a multi-byte corrupt payload (leading byte100→ zig-zag tag 50, out of range for 2 branches) throws mid-read and leaves trailing bytes in the decoder buffer; assertsvalid → corrupt throws → valid still succeeds.