PoolChunkList not correctly move PoolChunks when these are moved.
Motivation: When a PoolChunk needs to get moved to the previous PoolChunkList because of the minUsage / maxUsage constraints we always just moved it one level which is incorrect and so could lead to have PoolChunks in the wrong PoolChunkList (in respect to their minUsage / maxUsage settings). This then could have the effect that PoolChunks are not released / freed in a timely fashion and so. Modifications: - Correctly move PoolChunks between PoolChunkLists, which includes moving it multiple "levels". - Add unit test Result: Correctlty move the PoolChunk to PoolChunkList when it is freed, even if its multiple layers.
This commit is contained in:
parent
c453c3ce0d
commit
66ce014074
@ -75,23 +75,51 @@ final class PoolChunkList<T> implements PoolChunkListMetric {
|
|||||||
chunk.free(handle);
|
chunk.free(handle);
|
||||||
if (chunk.usage() < minUsage) {
|
if (chunk.usage() < minUsage) {
|
||||||
remove(chunk);
|
remove(chunk);
|
||||||
if (prevList == null) {
|
// Move the PoolChunk down the PoolChunkList linked-list.
|
||||||
assert chunk.usage() == 0;
|
return move0(chunk);
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
prevList.add(chunk);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean move(PoolChunk<T> chunk) {
|
||||||
|
assert chunk.usage() < maxUsage;
|
||||||
|
|
||||||
|
if (chunk.usage() < minUsage) {
|
||||||
|
// Move the PoolChunk down the PoolChunkList linked-list.
|
||||||
|
return move0(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolChunk fits into this PoolChunkList, adding it here.
|
||||||
|
add0(chunk);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the {@link PoolChunk} down the {@link PoolChunkList} linked-list so it will end up in the right
|
||||||
|
* {@link PoolChunkList} that has the correct minUsage / maxUsage in respect to {@link PoolChunk#usage()}.
|
||||||
|
*/
|
||||||
|
private boolean move0(PoolChunk<T> chunk) {
|
||||||
|
if (prevList == null) {
|
||||||
|
// There is no previous PoolChunkList so return false which result in having the PoolChunk destroyed and
|
||||||
|
// all memory associated with the PoolChunk will be released.
|
||||||
|
assert chunk.usage() == 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return prevList.move(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
void add(PoolChunk<T> chunk) {
|
void add(PoolChunk<T> chunk) {
|
||||||
if (chunk.usage() >= maxUsage) {
|
if (chunk.usage() >= maxUsage) {
|
||||||
nextList.add(chunk);
|
nextList.add(chunk);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
add0(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the {@link PoolChunk} to this {@link PoolChunkList}.
|
||||||
|
*/
|
||||||
|
void add0(PoolChunk<T> chunk) {
|
||||||
chunk.parent = this;
|
chunk.parent = this;
|
||||||
if (head == null) {
|
if (head == null) {
|
||||||
head = chunk;
|
head = chunk;
|
||||||
|
@ -31,6 +31,7 @@ import java.util.concurrent.locks.LockSupport;
|
|||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class PooledByteBufAllocatorTest {
|
public class PooledByteBufAllocatorTest {
|
||||||
@ -57,6 +58,35 @@ public class PooledByteBufAllocatorTest {
|
|||||||
assertEquals(max, m.maxUsage());
|
assertEquals(max, m.maxUsage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFreePoolChunk() {
|
||||||
|
int chunkSize = 16 * 1024 * 1024;
|
||||||
|
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true, 1, 0, 8192, 11, 0, 0, 0);
|
||||||
|
ByteBuf buffer = allocator.heapBuffer(chunkSize);
|
||||||
|
List<PoolArenaMetric> arenas = allocator.heapArenas();
|
||||||
|
assertEquals(1, arenas.size());
|
||||||
|
List<PoolChunkListMetric> lists = arenas.get(0).chunkLists();
|
||||||
|
assertEquals(6, lists.size());
|
||||||
|
|
||||||
|
assertFalse(lists.get(0).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(1).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(2).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(3).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(4).iterator().hasNext());
|
||||||
|
|
||||||
|
// Must end up in the 6th PoolChunkList
|
||||||
|
assertTrue(lists.get(5).iterator().hasNext());
|
||||||
|
assertTrue(buffer.release());
|
||||||
|
|
||||||
|
// Should be completely removed and so all PoolChunkLists must be empty
|
||||||
|
assertFalse(lists.get(0).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(1).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(2).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(3).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(4).iterator().hasNext());
|
||||||
|
assertFalse(lists.get(5).iterator().hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
// The ThreadDeathWatcher sleeps 1s, give it double that time.
|
// The ThreadDeathWatcher sleeps 1s, give it double that time.
|
||||||
@Test (timeout = 2000)
|
@Test (timeout = 2000)
|
||||||
public void testThreadCacheDestroyedByThreadDeathWatcher() {
|
public void testThreadCacheDestroyedByThreadDeathWatcher() {
|
||||||
|
Loading…
Reference in New Issue
Block a user