-
-
Notifications
You must be signed in to change notification settings - Fork 16.1k
Fix ref-counting when CompositeByteBuf is used with retainedSlice() #8497
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
Conversation
Can one of the admins verify this patch? |
Motivation: ByteBuf.retainedSlice() and similar methods produce sliced buffers with an independent refcount to the buffer that they wrap. One of the optimizations in 10539f4 was to use the ref to the unwrapped buffer object for added slices, but this did not take into account the above special case when later releasing. Thanks to @rkapsi for discovering this via netty#8495. Modifications: Since a reference to the slice is still kept in the Component class, just changed Component.freeIfNecessary() to release the slice in preference to the unwrapped buf. Also added a unit test which reproduces the bug. Result: Fixes netty#8495
fcc8824
to
38957b8
Compare
@netty-bot test this please |
Took the branch for a spin, no more Exceptions or ERRORs as reported in #8495 . |
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.
a few nits
// refcount to the unwrapped buf if it is a PooledSlicedByteBuf | ||
ByteBuf s = slice; | ||
(s != null ? s : buf).release(); | ||
// null out in both cases since it could be racy |
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.
I think this would be easier to read like this:
ByteBuf buffer = s == null ? buf : s;
buffer.release();
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.
@njhill I am confused... why did you need to to have s
and toRelease
here ? Which not just do what I suggested ?
Which would be:
ByteBuf buffer = slice == null ? buf : slice;
buffer.release();
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.
@normanmaurer oh, sorry... I misinterpreted your original suggestion since it used s
. Maybe I am being overly cautious, but just based on the fact that slice
gets nulled after the component is released I thought it would be safer to do the check on a local var (I ack that it probably is only possible to hit an NPE problem if there is another bug or incorrect use though).
Happy to change it to use slice
directly if you think that's fine...
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.
yeah I think I would go for the easier code or would at least not write it so compact to make it easier to understand
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.
done - changed to use if/else, hopefully this is the most readable of all :)
@@ -1144,6 +1144,36 @@ public void testReleasesItsComponents() { | |||
assertEquals(0, buffer.refCnt()); | |||
} | |||
|
|||
@Test | |||
public void testReleasesItsComponents2() { | |||
ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); // 1 |
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.
Can you add a comment that using the PooledByteBufAllocator is important here ?
ByteBuf s3 = s2.readRetainedSlice(2); // 4 | ||
ByteBuf s4 = s3.readRetainedSlice(2); // 5 | ||
|
||
ByteBuf composite = PooledByteBufAllocator.DEFAULT.compositeBuffer() |
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.
same as above
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.
@normanmaurer Actually I don't think this one would make a difference since it's only used if/when the composite buffer gets expanded right?
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.
@njhill ah yeah true... then fix it please to use Unpooled.
assertEquals(2, buffer.refCnt()); | ||
|
||
// releasing composite should release the 4 components | ||
ReferenceCountUtil.release(composite); |
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.
just use composite.release()
assertEquals(1, buffer.refCnt()); | ||
|
||
// last remaining ref to buffer | ||
ReferenceCountUtil.release(buffer); |
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.
just use buffer.release()
@rkapsi thanks a lot! |
f70b806
to
ac2b6c9
Compare
Thanks @normanmaurer, have pushed a commit addressing your comments. Re the use of |
@njhill I guess it would make sense to cleanup this stuff as a followup. |
@netty-bot test this please |
@njhill merged... thanks! |
Motivation: The special case fixed in netty#8497 also requires that we keep a derived slice when trimming components in place, as done by the capacity(int) and discardReadBytes() methods. Modifications: Ensure that we keep a ref to trimmed components' original retained slice in capacity(int) and discardReadBytes() methods, so that it is released properly when the they are later freed. Add unit test which fails prior to the fix. Result: Edge case leak is eliminated.
Motivation: The special case fixed in #8497 also requires that we keep a derived slice when trimming components in place, as done by the capacity(int) and discardReadBytes() methods. Modifications: Ensure that we keep a ref to trimmed components' original retained slice in capacity(int) and discardReadBytes() methods, so that it is released properly when the they are later freed. Add unit test which fails prior to the fix. Result: Edge case leak is eliminated.
Motivation: The special case fixed in #8497 also requires that we keep a derived slice when trimming components in place, as done by the capacity(int) and discardReadBytes() methods. Modifications: Ensure that we keep a ref to trimmed components' original retained slice in capacity(int) and discardReadBytes() methods, so that it is released properly when the they are later freed. Add unit test which fails prior to the fix. Result: Edge case leak is eliminated.
Motivation: The special case fixed in netty#8497 also requires that we keep a derived slice when trimming components in place, as done by the capacity(int) and discardReadBytes() methods. Modifications: Ensure that we keep a ref to trimmed components' original retained slice in capacity(int) and discardReadBytes() methods, so that it is released properly when the they are later freed. Add unit test which fails prior to the fix. Result: Edge case leak is eliminated.
Motivation:
ByteBuf.retainedSlice()
and similar methods produce sliced buffers with an independent refcount to the buffer that they wrap.One of the optimizations in 10539f4 was to use a ref to the unwrapped buffer object for added slices, but this did not take into account the above special case when later releasing.
Thanks to @rkapsi for discovering this via #8495.
Modifications:
Since a reference to the slice is still kept in the
Component
class, just changedComponent.freeIfNecessary()
to release the slice in preference to the unwrapped buf.Also added a unit test which reproduces the bug.
Result:
Fixes #8495