Skip to content

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

Merged
merged 3 commits into from
Nov 13, 2018

Conversation

njhill
Copy link
Member

@njhill njhill commented Nov 12, 2018

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 changed Component.freeIfNecessary() to release the slice in preference to the unwrapped buf.

Also added a unit test which reproduces the bug.

Result:

Fixes #8495

@netty-bot
Copy link

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
@njhill njhill force-pushed the composite-refcnt-fix branch from fcc8824 to 38957b8 Compare November 13, 2018 01:08
@normanmaurer
Copy link
Member

@netty-bot test this please

@rkapsi
Copy link
Member

rkapsi commented Nov 13, 2018

Took the branch for a spin, no more Exceptions or ERRORs as reported in #8495 .

Copy link
Member

@normanmaurer normanmaurer left a 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
Copy link
Member

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();

Copy link
Member

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();

Copy link
Member Author

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...

Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Member Author

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?

Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use buffer.release()

@normanmaurer
Copy link
Member

@rkapsi thanks a lot!

@normanmaurer normanmaurer added this to the 4.1.32.Final milestone Nov 13, 2018
@njhill njhill force-pushed the composite-refcnt-fix branch from f70b806 to ac2b6c9 Compare November 13, 2018 18:47
@njhill
Copy link
Member Author

njhill commented Nov 13, 2018

Thanks @normanmaurer, have pushed a commit addressing your comments.

Re the use of ReferenceCountUtil.release(buf) and pooled/unpooled compositeBuffer() - they seem to be used in many of the other tests, here I just copy/pasted the test above and changed readSlice().retain() to readRetainedSlice(). Just FYI that there's some inconsistency.

@normanmaurer
Copy link
Member

@njhill I guess it would make sense to cleanup this stuff as a followup.

@normanmaurer
Copy link
Member

@normanmaurer
Copy link
Member

@netty-bot test this please

@normanmaurer normanmaurer merged commit 804e1fa into netty:4.1 Nov 13, 2018
@normanmaurer
Copy link
Member

@njhill merged... thanks!

@njhill njhill deleted the composite-refcnt-fix branch November 13, 2018 20:23
njhill added a commit to njhill/netty that referenced this pull request Mar 15, 2019
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.
normanmaurer pushed a commit that referenced this pull request Mar 22, 2019
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.
normanmaurer pushed a commit that referenced this pull request Mar 22, 2019
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.
jtgrabowski pushed a commit to riptano/netty that referenced this pull request Oct 22, 2019
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants