-
Notifications
You must be signed in to change notification settings - Fork 286
Fix ByteBuf memory leak in blob deserialization error paths #3177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix ByteBuf memory leak in blob deserialization error paths #3177
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3177 +/- ##
=============================================
- Coverage 64.24% 31.36% -32.88%
+ Complexity 10398 5809 -4589
=============================================
Files 840 931 +91
Lines 71755 79180 +7425
Branches 8611 9430 +819
=============================================
- Hits 46099 24835 -21264
- Misses 23004 52360 +29356
+ Partials 2652 1985 -667 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
When CRC validation fails during blob deserialization in MessageFormatRecord, the ByteBuf allocated by Utils.readNettyByteBufFromCrcInputStream() was not being released, causing a memory leak. The issue affected all three blob format versions (V1, V2, V3) in their respective deserializeBlobRecord() methods. When corrupt data triggered a MessageFormatException during CRC validation, the allocated ByteBuf slice remained unreleased. Changes: - Extract CRC validation logic into validateCrcAndManageByteBuf() helper method - Implement try-finally pattern to ensure ByteBuf.release() on validation failure - Apply fix consistently across Blob_Format_V1, V2, and V3 deserializers - Add comprehensive leak detection tests using custom ByteBuf wrappers Tests: - MessageFormatCorruptDataLeakTest verifies ByteBuf cleanup on corrupt data - Parameterized tests cover all blob format versions (V1, V2, V3) - Control tests ensure no false positives on successful deserialization - Uses SliceCapturingByteBuf wrapper via DelegateByteBuf to track slice creation
4274356 to
52076f2
Compare
| throw new MessageFormatException("corrupt data while parsing blob content", | ||
| MessageFormatErrorCodes.DataCorrupt); | ||
| } | ||
| validateCrcAndManageByteBuf(crcStream, dataStream, byteBuf, logger); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, this one is tricky. The reason why we are not releasing this ByteBuf anywhere is because we didn't increase the refCounter when creating this ByteBuf. In readNettyByteBufFromCrcInputStream, we use slice method to create this ByteBuf, instead of retainedSlice.
If we call release on the failure here, then we would have to increase the refCounter by calling retainedSlice, but that would mean in the success path, we also have to call release one more time.
It does seems like a good idea to call retain/release here, since it shows the clear ownership of each ByteBuf. But in this case, we would have update other files on the success path to release this ByteBuf. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take a look at how this is handled in GetBlobOperation.handleBody() and subsequent calls. The chunkBuf within the BlobData is owned by the caller and it's their responsibility to release. If MessageFormatRecord.deserializeBlob() throws, GetBlobOperation.handleBody() never handles the bytebuf here or in the input stream.
In my read and testing of Utils.readNettyByteBufFromCrcInputStream() it always returns a Bytebuf passing the ownership (and the responsibility to release) to the caller. Thus if validateCrcAndManageByteBuf fails we must release here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's another path where we improve the memory management expectations in readNettyByteBufFromCrcInputStream and it's callers which is probably preferred but a slightly larger change. Let me pull this back to draft and spend some more time in testing that approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readByteBufferFromCrcInputStream methods which return ByteBuffer (which are not part of this change) would require a significant code change as ByteBuffer doesn't have refCnt semantics. If we decide to do that work it should happen in a new PR. Made the minimal change for this fix.
6ef90c6 to
7be3272
Compare
|
The more I work on this the more I see work that needs to be done. Pulling this back to draft for now. |
Summary
When CRC validation fails during blob deserialization in MessageFormatRecord, the bytebuf allocated by Utils.readNettyByteBufFromCrcInputStream() was not being released, causing a memory leak.
The issue affected all three blob format versions (V1, V2, V3) in their respective deserializeBlobRecord() methods. When corrupt data triggered a MessageFormatException during CRC validation, the allocated bytebuf slice remained unreleased.
Changes:
Testing Done