Skip to content

Commit fcaf37f

Browse files
njhilljtgrabowski
authored andcommitted
Fix possible ByteBuf leak when CompositeByteBuf is resized (netty#8946)
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.
1 parent 11fbd25 commit fcaf37f

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

buffer/src/main/java/io/netty/buffer/CompositeByteBuf.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,12 @@ public CompositeByteBuf capacity(int newCapacity) {
753753
if (bytesToTrim < cLength) {
754754
// Trim the last component
755755
c.endOffset -= bytesToTrim;
756-
c.slice = null;
756+
ByteBuf slice = c.slice;
757+
if (slice != null) {
758+
// We must replace the cached slice with a derived one to ensure that
759+
// it can later be released properly in the case of PooledSlicedByteBuf.
760+
c.slice = slice.slice(0, c.length());
761+
}
757762
break;
758763
}
759764
c.free();
@@ -1732,10 +1737,16 @@ public CompositeByteBuf discardReadBytes() {
17321737
}
17331738
firstComponentId++;
17341739
} else {
1740+
int trimmedBytes = readerIndex - c.offset;
17351741
c.offset = 0;
17361742
c.endOffset -= readerIndex;
17371743
c.adjustment += readerIndex;
1738-
c.slice = null;
1744+
ByteBuf slice = c.slice;
1745+
if (slice != null) {
1746+
// We must replace the cached slice with a derived one to ensure that
1747+
// it can later be released properly in the case of PooledSlicedByteBuf.
1748+
c.slice = slice.slice(trimmedBytes, c.length());
1749+
}
17391750
}
17401751

17411752
removeCompRange(0, firstComponentId);

buffer/src/test/java/io/netty/buffer/AbstractCompositeByteBufTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,40 @@ public void testReleasesOnShrink() {
12521252
assertEquals(0, b2.refCnt());
12531253
}
12541254

1255+
@Test
1256+
public void testReleasesOnShrink2() {
1257+
// It is important to use a pooled allocator here to ensure
1258+
// the slices returned by readRetainedSlice are of type
1259+
// PooledSlicedByteBuf, which maintains an independent refcount
1260+
// (so that we can be sure to cover this case)
1261+
ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer();
1262+
1263+
buffer.writeShort(1).writeShort(2);
1264+
1265+
ByteBuf b1 = buffer.readRetainedSlice(2);
1266+
ByteBuf b2 = b1.retainedSlice(b1.readerIndex(), 2);
1267+
1268+
// composite takes ownership of b1 and b2
1269+
ByteBuf composite = Unpooled.compositeBuffer()
1270+
.addComponents(b1, b2);
1271+
1272+
assertEquals(4, composite.capacity());
1273+
1274+
// reduce capacity down to two, will drop the second component
1275+
composite.capacity(2);
1276+
assertEquals(2, composite.capacity());
1277+
1278+
// releasing composite should release the components
1279+
composite.release();
1280+
assertEquals(0, composite.refCnt());
1281+
assertEquals(0, b1.refCnt());
1282+
assertEquals(0, b2.refCnt());
1283+
1284+
// release last remaining ref to buffer
1285+
buffer.release();
1286+
assertEquals(0, buffer.refCnt());
1287+
}
1288+
12551289
@Test
12561290
public void testAllocatorIsSameWhenCopy() {
12571291
testAllocatorIsSameWhenCopy(false);

0 commit comments

Comments
 (0)