[format] Close the Avro stats extractor's stream on a corrupt file - #9573
Merged
Merged
Conversation
extractWithFileInfo opened the file and handed the stream to getRowCount, whose try-with-resources binds the DataFileStream, not the stream. When the DataFileStream constructor throws, nothing holds the input any more: a non-Avro file fails the magic check, an empty or truncated file hits EOF in readMagic, and an unrecognised codec makes CodecFactory.fromString throw AvroRuntimeException. Close it on the failure path, the way AvroBulkFormat.createReaderFromPath in the same package already does. Catching Throwable rather than IOException is deliberate: the codec case throws a RuntimeException. Closing only on failure rather than wrapping the whole method in try-with-resources keeps the success path at one close, since DataFileStream.close closes the stream it was given. Only migrate and clone reach this extractor with a file Paimon did not write. Both write paths skip it: RollingFileWriter.createStatsProducer and KeyValueFileWriterFactory.statsProducer build stats from the collector for avro. So one attempt leaks one descriptor. The tests count closes rather than recording a boolean, so they also pin the success path at exactly one close. Three of the four fail against the unfixed extractor; the valid-file one passes there too, which is the point of it. Assisted-by: GLM-5.3
Contributor
|
+1 |
Contributor
Author
|
Thank you @JingsongLi |
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.
Purpose
close #9572
AvroSimpleStatsExtractor.extractWithFileInfoopens the file and hands the stream togetRowCount, whose try-with-resources binds theDataFileStreamrather than the stream:When the
DataFileStreamconstructor throws, the resource was never bound and no local holds the input, so nothing closes it. Three ways it throws: a non-Avro file fails the magic check, an empty or truncated file hits EOF inreadMagic, and an unrecognisedavro.codecmakesCodecFactory.fromStringthrow. This closes the stream on the failure path, the wayAvroBulkFormat.createReaderFromPathin the same package already does.Two choices worth calling out. The catch is on
Throwable, mirroringAvroBulkFormatin the same package; it has to be at leastRuntimeException, because the codec case is unchecked andcatch (IOException)would miss it. And closing only on failure rather than wrapping the method in try-with-resources, so the success path still closes exactly once:DataFileStream.closecloses the stream it was given, and an outer try-with-resources would close it a second time. The implementations I looked at are idempotent, so a double close is harmless today, but it depends on that and the tests below pin the single close instead.Only
migrateand clone reach this extractor with a file Paimon did not write; both write paths build stats from the collector for avro (RollingFileWriter.createStatsProducer,KeyValueFileWriterFactory.statsProducer). One attempt leaks one descriptor. What makes it worth fixing is that the corrupt-file path is not exotic: a zero-byte file left by a failed task is an ordinary thing to find in a Hive table directory, and the migrate scan only skips names beginning with_or..Tests
AvroSimpleStatsExtractorLeakTestwrapsLocalFileIOso every stream counts its ownclose()calls, and asserts exactly one close in all four cases rather than just "was closed": a non-Avro file, an empty file, an unrecognised codec, and a valid three-row file. The codec case takes a real zstd file and renameszstandardtozstandarXin the header, keeping the length so the header still parses and the failure comes fromCodecFactory.fromString; it assertsAvroRuntimeExceptionwith that message, since that case is the whole reason the catch is onThrowable.The three corrupt cases fail against the unfixed extractor. The valid-file case passes there too, which is the point of it: it guards the single close, which is what a return to try-with-resources would break.
mvn -pl paimon-format teston JDK 8: 600 tests, 0 failures.spotless:checkandcheckstyle:checkare clean.