Repository navigation
Release the in-flight field when a post decoder is destroyed - #17389
HuzaifaChaudary wants to merge 7 commits into
Conversation
destroy() releases everything in bodyListHttpData, but a field that is still being decoded was never put there yet . with a memory only factory it is not tracked by the factory either , so cleanFiles() does not reach it and it is simply lost . this happens on the normal case where a client goes away in the middle of a form post and the server calls destroy() . release currentAttribute , and currentFileUpload in the multipart decoder , the same way the list is released .
| // A HttpData that is still being decoded was never added to bodyListHttpData, and a memory based | ||
| // one is not tracked by the factory either, so cleanFiles() above does not cover it. | ||
| if (currentFileUpload != null) { | ||
| if (currentFileUpload.refCnt() > 0) { |
There was a problem hiding this comment.
why do we need to check the reference count ? Same question for the other release calls
There was a problem hiding this comment.
cleanFiles() runs two lines above this and it releases everything the factory is holding , without checking . the default DefaultHttpDataFactory has checkSize true , so createAttribute and createFileUpload put the in progress one on that list , and by the time destroy() gets down here the count is already 0 . so the check is not the "user might have released it" reason that the loop below has , it is the release that just happened a few lines up .
i took all three checks out and ran the class : 9 errors , every one of them IllegalReferenceCountException: refCnt: 0, decrement: 1 . seven are tests that were already there , testTooManyFormFieldsPostStandardDecoder , testFieldGreaterThanMaxBufferedBytesStandardDecoder and the four broken hex byte ones . with the checks back it is 42 of 42 .
worth saying that my two tests would not have caught that . they use new DefaultHttpDataFactory(false) , the memory only path where the factory does not track it at all . i have pushed two more that use the default factory so the tracked side is covered as well .
|
Do you have an application that is buggy because of this? Reference counting in the POST decoders is a mess, and frameworks have all sorts of workarounds for it. I would be hesitant to accept a change that may break those frameworks, even if this could be considered a bug. |
the three tests already here build the factory directly. the case that reaches this in a server is a memory only factory that overrides only createFileUpload, which is the shape of vert.x's NettyFileUploadDataFactory, with the buffered bytes limit tripping while a field is still being decoded. that is the path a framework takes when it catches the decoder exception and calls destroy() in response. fails without the change with expected: <0> but was: <1>, and the leak presence extension raises Possible memory leak detected on the class on top of it.
|
no, i do not have a production application that this is breaking. what i have is the who is in the leaking configuration. vert.x's i pushed a fourth test in that shape , memory only factory overriding only the upload on breaking the workarounds. the only way the in flight item leaves the decoder is
the break i would worry about is a framework that keeps the in flight item past scope, for what it is worth: the default factory, and #8045 is the same leak from the other side. the answer there was "You need to call |
…h refCnt Remove the item from the factory before cleanFiles(), so a tracking (mixed/disk) factory does not release it first, then release it unconditionally. The in-progress item is never handed out (current* is cleared as soon as it is added to bodyListHttpData), so the decoder is its sole owner and no refCnt check is needed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
yawkat
left a comment
There was a problem hiding this comment.
claude found an alternative solution without refCnt
🚨 All tests passed but jobs failed 🚨Failed Jobs without Test Failures❌ Build PR / linux-x86_64-java11-awslc build 🏷️ Commit: 9d5b9df Rerun ControlsClick the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app/docs. |
|
I was wrong :( the guard is necessary. |
The decoder is not the only one that can release the in-progress HttpData: it is handed out by currentPartialHttpData(), a tracking factory releases it on cleanFiles() or when the user cleans the factory directly. destroy() must not release it a second time in these cases. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
merged 4.2 in to clear the conflict . it was only the test file , both sides had added tests at the end of |
Motivation
destroy()on both post decoders releases everything inbodyListHttpDataand asks thefactory to clean up , but the field that is still being decoded is in neither .
setFinalBuffer()only callsaddHttpData()once a field terminates , so a partial one neverreaches
bodyListHttpData. andDefaultHttpDataFactory.createAttributeonly puts it ingetList(request)on theuseDiskandcheckSizebranches . the memory fallthrough returns abare
MemoryAttributethat nothing tracks .so on the ordinary path where a client goes away in the middle of a form post and the server
calls
decoder.destroy(), the in flightAttributeorFileUploadleaks , along with theretained slice of the inbound buffer it holds . netty's own leak detector fires on it .
this only bites when the factory hands back untracked memory data , which is
new DefaultHttpDataFactory(false), the documented always in Memory mode , or a customfactory . the default constructor ,
MINSIZEanduseDisk=trueall already reach refCnt 0 ,i checked all three .
#11188 added that
bodyListHttpDatarelease loop sayingdestroy()should clean up memorybased data too , it just did not cover the item still in progress .
Modification
release
currentAttribute, andcurrentFileUploadin the multipart decoder , at the end ofdestroy(). therefCnt() > 0guard matches the loop just above it , so a disk or mixed itemthat
cleanFiles()already freed is not released twice .i did not go the other way and make the factory track memory data . that was tried in #10432
and closed as a breaking change , and #11109 was closed with MemoryAttribute is not concerned
by this method . nothing here touches the factory .
Result
no leak from a decoder destroyed mid field .
two tests added . both fail without the change with
expected: <0> but was: <1>, and the leakdetector raises
Possible memory leak detectedon top , so they are not passing off their ownassertions .
codec-httpis 8985 tests , 0 failures . checkstyle and forbiddenapis are clean on the module .the same
destroy()is on 4.1 , so this probably wantsneeds-cherry-pick-4.1.