Search before asking
Fluss version
main (development)
Please describe the bug 🐞
The log scanner close chain leaks off-heap Arrow memory when any intermediate close() fails, because each layer closes resources sequentially and aborts on the first exception.
Three leak paths:
-
LogScannerImpl.pollRecordBatch() — sendFetches() is called after scanRecords is already materialized but without a try-catch. If sendFetches() throws (e.g., OSS QpsLimitExceeded during Paimon write), the polled Arrow batches are neither returned to the caller nor closed.
-
LogFetcher.close() — iterates tableReadContexts in a plain for loop. Any failing TableReadContext.close() skips the remaining contexts and the shared chunkedFactory. Because isClosed is already true, the pooled native chunks are never released.
-
LogRecordReadContext.close() — if bufferAllocator.close() throws due to outstanding Arrow allocations, unshadedBufferAllocator is neither closed nor nulled. Same sequential-skip issue in TableReadContext.close(): readContext.close() failing prevents remoteReadContext.close().
This is a follow-up to #3719, which fixed lake writer leaks in TieringSplitReader but did not address the deeper scanner close chain.
Solution
Use IOUtils.closeAll() at each layer to close all resources regardless of individual failures, and clear references in finally blocks. Wrap sendFetches() in try-catch to release polled batches on write failure.
Are you willing to submit a PR?
Search before asking
Fluss version
main (development)
Please describe the bug 🐞
The log scanner close chain leaks off-heap Arrow memory when any intermediate
close()fails, because each layer closes resources sequentially and aborts on the first exception.Three leak paths:
LogScannerImpl.pollRecordBatch()—sendFetches()is called afterscanRecordsis already materialized but without a try-catch. IfsendFetches()throws (e.g., OSSQpsLimitExceededduring Paimon write), the polled Arrow batches are neither returned to the caller nor closed.LogFetcher.close()— iteratestableReadContextsin a plainforloop. Any failingTableReadContext.close()skips the remaining contexts and the sharedchunkedFactory. BecauseisClosedis alreadytrue, the pooled native chunks are never released.LogRecordReadContext.close()— ifbufferAllocator.close()throws due to outstanding Arrow allocations,unshadedBufferAllocatoris neither closed nor nulled. Same sequential-skip issue inTableReadContext.close():readContext.close()failing preventsremoteReadContext.close().This is a follow-up to #3719, which fixed lake writer leaks in
TieringSplitReaderbut did not address the deeper scanner close chain.Solution
Use
IOUtils.closeAll()at each layer to close all resources regardless of individual failures, and clear references infinallyblocks. WrapsendFetches()in try-catch to release polled batches on write failure.Are you willing to submit a PR?