2015-10-22 22:09:41 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2015 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package io.netty.buffer;
|
|
|
|
|
2016-03-14 17:25:43 +01:00
|
|
|
import io.netty.util.concurrent.FastThreadLocal;
|
|
|
|
import io.netty.util.concurrent.FastThreadLocalThread;
|
2017-02-15 13:19:31 +01:00
|
|
|
import io.netty.util.internal.PlatformDependent;
|
2015-10-22 22:09:41 +02:00
|
|
|
import io.netty.util.internal.SystemPropertyUtil;
|
2017-02-13 07:42:22 +01:00
|
|
|
import org.junit.Assume;
|
2015-10-22 22:09:41 +02:00
|
|
|
import org.junit.Test;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Queue;
|
2016-11-23 13:26:56 +01:00
|
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
2015-10-22 22:09:41 +02:00
|
|
|
import java.util.concurrent.CountDownLatch;
|
2016-03-15 15:40:09 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
2016-11-23 13:26:56 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
2016-03-14 17:25:43 +01:00
|
|
|
import java.util.concurrent.locks.LockSupport;
|
|
|
|
|
|
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
2016-04-05 17:59:56 +02:00
|
|
|
import static org.junit.Assert.assertFalse;
|
2017-02-23 10:58:19 +01:00
|
|
|
import static org.junit.Assert.assertNotNull;
|
2016-03-14 17:25:43 +01:00
|
|
|
import static org.junit.Assert.assertTrue;
|
2015-10-22 22:09:41 +02:00
|
|
|
|
2017-02-24 20:06:17 +01:00
|
|
|
public class PooledByteBufAllocatorTest extends AbstractByteBufAllocatorTest<PooledByteBufAllocator> {
|
2017-01-26 12:15:10 +01:00
|
|
|
|
|
|
|
@Override
|
2017-02-24 20:06:17 +01:00
|
|
|
protected PooledByteBufAllocator newAllocator(boolean preferDirect) {
|
2017-01-26 12:15:10 +01:00
|
|
|
return new PooledByteBufAllocator(preferDirect);
|
|
|
|
}
|
2015-10-22 22:09:41 +02:00
|
|
|
|
2017-02-15 13:19:31 +01:00
|
|
|
@Override
|
2017-02-24 20:06:17 +01:00
|
|
|
protected PooledByteBufAllocator newUnpooledAllocator() {
|
2017-02-15 13:19:31 +01:00
|
|
|
return new PooledByteBufAllocator(0, 0, 8192, 1);
|
|
|
|
}
|
|
|
|
|
2017-02-24 20:06:17 +01:00
|
|
|
@Override
|
|
|
|
protected long expectedUsedMemory(PooledByteBufAllocator allocator, int capacity) {
|
2017-03-02 08:50:47 +01:00
|
|
|
return allocator.metric().chunkSize();
|
2017-02-24 20:06:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected long expectedUsedMemoryAfterRelease(PooledByteBufAllocator allocator, int capacity) {
|
|
|
|
// This is the case as allocations will start in qInit and chunks in qInit will never be released until
|
|
|
|
// these are moved to q000.
|
|
|
|
// See https://www.bsdcan.org/2006/papers/jemalloc.pdf
|
2017-03-02 08:50:47 +01:00
|
|
|
return allocator.metric().chunkSize();
|
2017-02-24 20:06:17 +01:00
|
|
|
}
|
|
|
|
|
2019-03-22 11:08:37 +01:00
|
|
|
@Test
|
|
|
|
public void testTrim() {
|
|
|
|
PooledByteBufAllocator allocator = newAllocator(true);
|
|
|
|
|
|
|
|
// Should return false as we never allocated from this thread yet.
|
|
|
|
assertFalse(allocator.trimCurrentThreadCache());
|
|
|
|
|
|
|
|
ByteBuf directBuffer = allocator.directBuffer();
|
|
|
|
|
|
|
|
assertTrue(directBuffer.release());
|
|
|
|
|
|
|
|
// Should return true now a cache exists for the calling thread.
|
|
|
|
assertTrue(allocator.trimCurrentThreadCache());
|
|
|
|
}
|
|
|
|
|
2017-02-15 13:19:31 +01:00
|
|
|
@Test
|
|
|
|
public void testPooledUnsafeHeapBufferAndUnsafeDirectBuffer() {
|
2017-02-24 20:06:17 +01:00
|
|
|
PooledByteBufAllocator allocator = newAllocator(true);
|
2017-02-15 13:19:31 +01:00
|
|
|
ByteBuf directBuffer = allocator.directBuffer();
|
|
|
|
assertInstanceOf(directBuffer,
|
|
|
|
PlatformDependent.hasUnsafe() ? PooledUnsafeDirectByteBuf.class : PooledDirectByteBuf.class);
|
|
|
|
directBuffer.release();
|
|
|
|
|
|
|
|
ByteBuf heapBuffer = allocator.heapBuffer();
|
|
|
|
assertInstanceOf(heapBuffer,
|
|
|
|
PlatformDependent.hasUnsafe() ? PooledUnsafeHeapByteBuf.class : PooledHeapByteBuf.class);
|
|
|
|
heapBuffer.release();
|
|
|
|
}
|
|
|
|
|
2019-05-22 07:32:41 +02:00
|
|
|
@Test
|
|
|
|
public void testIOBuffersAreDirectWhenUnsafeAvailableOrDirectBuffersPooled() {
|
|
|
|
PooledByteBufAllocator allocator = newAllocator(true);
|
|
|
|
ByteBuf ioBuffer = allocator.ioBuffer();
|
|
|
|
|
|
|
|
assertTrue(ioBuffer.isDirect());
|
|
|
|
ioBuffer.release();
|
|
|
|
|
|
|
|
PooledByteBufAllocator unpooledAllocator = newUnpooledAllocator();
|
|
|
|
ioBuffer = unpooledAllocator.ioBuffer();
|
|
|
|
|
|
|
|
if (PlatformDependent.hasUnsafe()) {
|
|
|
|
assertTrue(ioBuffer.isDirect());
|
|
|
|
} else {
|
|
|
|
assertFalse(ioBuffer.isDirect());
|
|
|
|
}
|
|
|
|
ioBuffer.release();
|
|
|
|
}
|
|
|
|
|
2017-09-08 04:40:36 +02:00
|
|
|
@Test
|
|
|
|
public void testWithoutUseCacheForAllThreads() {
|
|
|
|
assertFalse(Thread.currentThread() instanceof FastThreadLocalThread);
|
|
|
|
|
|
|
|
PooledByteBufAllocator pool = new PooledByteBufAllocator(
|
|
|
|
/*preferDirect=*/ false,
|
|
|
|
/*nHeapArena=*/ 1,
|
|
|
|
/*nDirectArena=*/ 1,
|
|
|
|
/*pageSize=*/8192,
|
|
|
|
/*maxOrder=*/ 11,
|
|
|
|
/*tinyCacheSize=*/ 0,
|
|
|
|
/*smallCacheSize=*/ 0,
|
|
|
|
/*normalCacheSize=*/ 0,
|
|
|
|
/*useCacheForAllThreads=*/ false);
|
|
|
|
ByteBuf buf = pool.buffer(1);
|
|
|
|
buf.release();
|
|
|
|
}
|
|
|
|
|
2016-05-20 10:44:29 +02:00
|
|
|
@Test
|
|
|
|
public void testArenaMetricsNoCache() {
|
|
|
|
testArenaMetrics0(new PooledByteBufAllocator(true, 2, 2, 8192, 11, 0, 0, 0), 100, 0, 100, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testArenaMetricsCache() {
|
|
|
|
testArenaMetrics0(new PooledByteBufAllocator(true, 2, 2, 8192, 11, 1000, 1000, 1000), 100, 1, 1, 0);
|
|
|
|
}
|
|
|
|
|
2017-01-29 22:26:40 +01:00
|
|
|
@Test
|
|
|
|
public void testArenaMetricsNoCacheAlign() {
|
2017-02-14 08:17:33 +01:00
|
|
|
Assume.assumeTrue(PooledByteBufAllocator.isDirectMemoryCacheAlignmentSupported());
|
2017-01-29 22:26:40 +01:00
|
|
|
testArenaMetrics0(new PooledByteBufAllocator(true, 2, 2, 8192, 11, 0, 0, 0, true, 64), 100, 0, 100, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testArenaMetricsCacheAlign() {
|
2017-02-13 07:42:22 +01:00
|
|
|
Assume.assumeTrue(PooledByteBufAllocator.isDirectMemoryCacheAlignmentSupported());
|
2017-01-29 22:26:40 +01:00
|
|
|
testArenaMetrics0(new PooledByteBufAllocator(true, 2, 2, 8192, 11, 1000, 1000, 1000, true, 64), 100, 1, 1, 0);
|
|
|
|
}
|
|
|
|
|
2016-05-20 10:44:29 +02:00
|
|
|
private static void testArenaMetrics0(
|
|
|
|
PooledByteBufAllocator allocator, int num, int expectedActive, int expectedAlloc, int expectedDealloc) {
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
assertTrue(allocator.directBuffer().release());
|
|
|
|
assertTrue(allocator.heapBuffer().release());
|
|
|
|
}
|
|
|
|
|
2017-03-02 08:50:47 +01:00
|
|
|
assertArenaMetrics(allocator.metric().directArenas(), expectedActive, expectedAlloc, expectedDealloc);
|
|
|
|
assertArenaMetrics(allocator.metric().heapArenas(), expectedActive, expectedAlloc, expectedDealloc);
|
2016-05-20 10:44:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static void assertArenaMetrics(
|
|
|
|
List<PoolArenaMetric> arenaMetrics, int expectedActive, int expectedAlloc, int expectedDealloc) {
|
|
|
|
int active = 0;
|
|
|
|
int alloc = 0;
|
|
|
|
int dealloc = 0;
|
|
|
|
for (PoolArenaMetric arena : arenaMetrics) {
|
|
|
|
active += arena.numActiveAllocations();
|
|
|
|
alloc += arena.numAllocations();
|
|
|
|
dealloc += arena.numDeallocations();
|
|
|
|
}
|
|
|
|
assertEquals(expectedActive, active);
|
|
|
|
assertEquals(expectedAlloc, alloc);
|
|
|
|
assertEquals(expectedDealloc, dealloc);
|
|
|
|
}
|
|
|
|
|
2016-04-05 19:50:34 +02:00
|
|
|
@Test
|
|
|
|
public void testPoolChunkListMetric() {
|
2017-03-02 08:50:47 +01:00
|
|
|
for (PoolArenaMetric arenaMetric: PooledByteBufAllocator.DEFAULT.metric().heapArenas()) {
|
2016-04-05 19:50:34 +02:00
|
|
|
assertPoolChunkListMetric(arenaMetric);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void assertPoolChunkListMetric(PoolArenaMetric arenaMetric) {
|
|
|
|
List<PoolChunkListMetric> lists = arenaMetric.chunkLists();
|
|
|
|
assertEquals(6, lists.size());
|
|
|
|
assertPoolChunkListMetric(lists.get(0), 1, 25);
|
|
|
|
assertPoolChunkListMetric(lists.get(1), 1, 50);
|
|
|
|
assertPoolChunkListMetric(lists.get(2), 25, 75);
|
|
|
|
assertPoolChunkListMetric(lists.get(4), 75, 100);
|
|
|
|
assertPoolChunkListMetric(lists.get(5), 100, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void assertPoolChunkListMetric(PoolChunkListMetric m, int min, int max) {
|
|
|
|
assertEquals(min, m.minUsage());
|
|
|
|
assertEquals(max, m.maxUsage());
|
|
|
|
}
|
|
|
|
|
2016-07-10 12:54:30 +02:00
|
|
|
@Test
|
|
|
|
public void testSmallSubpageMetric() {
|
|
|
|
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true, 1, 1, 8192, 11, 0, 0, 0);
|
|
|
|
ByteBuf buffer = allocator.heapBuffer(500);
|
|
|
|
try {
|
2017-03-02 08:50:47 +01:00
|
|
|
PoolArenaMetric metric = allocator.metric().heapArenas().get(0);
|
2016-07-10 12:54:30 +02:00
|
|
|
PoolSubpageMetric subpageMetric = metric.smallSubpages().get(0);
|
|
|
|
assertEquals(1, subpageMetric.maxNumElements() - subpageMetric.numAvailable());
|
|
|
|
} finally {
|
|
|
|
buffer.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testTinySubpageMetric() {
|
|
|
|
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true, 1, 1, 8192, 11, 0, 0, 0);
|
|
|
|
ByteBuf buffer = allocator.heapBuffer(1);
|
|
|
|
try {
|
2017-03-02 08:50:47 +01:00
|
|
|
PoolArenaMetric metric = allocator.metric().heapArenas().get(0);
|
2016-07-10 12:54:30 +02:00
|
|
|
PoolSubpageMetric subpageMetric = metric.tinySubpages().get(0);
|
|
|
|
assertEquals(1, subpageMetric.maxNumElements() - subpageMetric.numAvailable());
|
|
|
|
} finally {
|
|
|
|
buffer.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-23 10:58:19 +01:00
|
|
|
@Test
|
|
|
|
public void testAllocNotNull() {
|
|
|
|
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true, 1, 1, 8192, 11, 0, 0, 0);
|
|
|
|
// Huge allocation
|
2017-03-02 08:50:47 +01:00
|
|
|
testAllocNotNull(allocator, allocator.metric().chunkSize() + 1);
|
2017-02-23 10:58:19 +01:00
|
|
|
// Normal allocation
|
|
|
|
testAllocNotNull(allocator, 1024);
|
|
|
|
// Small allocation
|
|
|
|
testAllocNotNull(allocator, 512);
|
|
|
|
// Tiny allocation
|
|
|
|
testAllocNotNull(allocator, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void testAllocNotNull(PooledByteBufAllocator allocator, int capacity) {
|
|
|
|
ByteBuf buffer = allocator.heapBuffer(capacity);
|
|
|
|
assertNotNull(buffer.alloc());
|
|
|
|
assertTrue(buffer.release());
|
|
|
|
assertNotNull(buffer.alloc());
|
|
|
|
}
|
|
|
|
|
2016-04-05 17:59:56 +02:00
|
|
|
@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);
|
2017-03-02 08:50:47 +01:00
|
|
|
List<PoolArenaMetric> arenas = allocator.metric().heapArenas();
|
2016-04-05 17:59:56 +02:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2017-11-27 13:53:12 +01:00
|
|
|
@Test (timeout = 4000)
|
2017-12-01 16:37:30 +01:00
|
|
|
public void testThreadCacheDestroyedByThreadCleaner() throws InterruptedException {
|
|
|
|
testThreadCacheDestroyed(false);
|
2017-11-27 13:53:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test (timeout = 4000)
|
|
|
|
public void testThreadCacheDestroyedAfterExitRun() throws InterruptedException {
|
2017-12-01 16:37:30 +01:00
|
|
|
testThreadCacheDestroyed(true);
|
2017-11-27 13:53:12 +01:00
|
|
|
}
|
|
|
|
|
2017-12-01 16:37:30 +01:00
|
|
|
private static void testThreadCacheDestroyed(boolean useRunnable) throws InterruptedException {
|
2016-03-14 17:25:43 +01:00
|
|
|
int numArenas = 11;
|
|
|
|
final PooledByteBufAllocator allocator =
|
|
|
|
new PooledByteBufAllocator(numArenas, numArenas, 8192, 1);
|
|
|
|
|
2016-03-15 15:40:09 +01:00
|
|
|
final AtomicBoolean threadCachesCreated = new AtomicBoolean(true);
|
|
|
|
|
2017-11-27 13:53:12 +01:00
|
|
|
final Runnable task = new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2017-12-01 16:37:30 +01:00
|
|
|
ByteBuf buf = allocator.newHeapBuffer(1024, 1024);
|
|
|
|
for (int i = 0; i < buf.capacity(); i++) {
|
|
|
|
buf.writeByte(0);
|
|
|
|
}
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2017-12-01 16:37:30 +01:00
|
|
|
// Make sure that thread caches are actually created,
|
|
|
|
// so that down below we are not testing for zero
|
|
|
|
// thread caches without any of them ever having been initialized.
|
|
|
|
if (allocator.metric().numThreadLocalCaches() == 0) {
|
|
|
|
threadCachesCreated.set(false);
|
2016-03-14 17:25:43 +01:00
|
|
|
}
|
2017-12-01 16:37:30 +01:00
|
|
|
|
|
|
|
buf.release();
|
2017-11-27 13:53:12 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i < numArenas; i++) {
|
|
|
|
final FastThreadLocalThread thread;
|
|
|
|
if (useRunnable) {
|
|
|
|
thread = new FastThreadLocalThread(task);
|
|
|
|
assertTrue(thread.willCleanupFastThreadLocals());
|
|
|
|
} else {
|
|
|
|
thread = new FastThreadLocalThread() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
task.run();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
assertFalse(thread.willCleanupFastThreadLocals());
|
|
|
|
}
|
|
|
|
thread.start();
|
2017-12-01 16:37:30 +01:00
|
|
|
thread.join();
|
2016-03-14 17:25:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the ThreadDeathWatcher to have destroyed all thread caches
|
2017-03-02 08:50:47 +01:00
|
|
|
while (allocator.metric().numThreadLocalCaches() > 0) {
|
2017-12-01 16:37:30 +01:00
|
|
|
// Signal we want to have a GC run to ensure we can process our ThreadCleanerReference
|
|
|
|
System.gc();
|
|
|
|
System.runFinalization();
|
2016-03-14 17:25:43 +01:00
|
|
|
LockSupport.parkNanos(MILLISECONDS.toNanos(100));
|
2015-10-22 22:09:41 +02:00
|
|
|
}
|
2016-03-15 15:40:09 +01:00
|
|
|
|
|
|
|
assertTrue(threadCachesCreated.get());
|
2015-10-22 22:09:41 +02:00
|
|
|
}
|
|
|
|
|
2016-05-17 18:23:29 +02:00
|
|
|
@Test(timeout = 3000)
|
2016-05-11 11:53:31 +02:00
|
|
|
public void testNumThreadCachesWithNoDirectArenas() throws InterruptedException {
|
2016-03-14 17:25:43 +01:00
|
|
|
int numHeapArenas = 1;
|
|
|
|
final PooledByteBufAllocator allocator =
|
|
|
|
new PooledByteBufAllocator(numHeapArenas, 0, 8192, 1);
|
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
ThreadCache tcache0 = createNewThreadCache(allocator);
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(1, allocator.metric().numThreadLocalCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
ThreadCache tcache1 = createNewThreadCache(allocator);
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(2, allocator.metric().numThreadLocalCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
tcache0.destroy();
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(1, allocator.metric().numThreadLocalCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
tcache1.destroy();
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(0, allocator.metric().numThreadLocalCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
}
|
|
|
|
|
2016-05-17 18:23:29 +02:00
|
|
|
@Test(timeout = 3000)
|
2016-03-14 17:25:43 +01:00
|
|
|
public void testThreadCacheToArenaMappings() throws InterruptedException {
|
|
|
|
int numArenas = 2;
|
|
|
|
final PooledByteBufAllocator allocator =
|
|
|
|
new PooledByteBufAllocator(numArenas, numArenas, 8192, 1);
|
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
ThreadCache tcache0 = createNewThreadCache(allocator);
|
|
|
|
ThreadCache tcache1 = createNewThreadCache(allocator);
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(2, allocator.metric().numThreadLocalCaches());
|
|
|
|
assertEquals(1, allocator.metric().heapArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().heapArenas().get(1).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().directArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().directArenas().get(0).numThreadCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
tcache1.destroy();
|
|
|
|
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(1, allocator.metric().numThreadLocalCaches());
|
|
|
|
assertEquals(1, allocator.metric().heapArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(0, allocator.metric().heapArenas().get(1).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().directArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(0, allocator.metric().directArenas().get(1).numThreadCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
ThreadCache tcache2 = createNewThreadCache(allocator);
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(2, allocator.metric().numThreadLocalCaches());
|
|
|
|
assertEquals(1, allocator.metric().heapArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().heapArenas().get(1).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().directArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(1, allocator.metric().directArenas().get(1).numThreadCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
tcache0.destroy();
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(1, allocator.metric().numThreadLocalCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
tcache2.destroy();
|
2017-03-02 08:50:47 +01:00
|
|
|
assertEquals(0, allocator.metric().numThreadLocalCaches());
|
|
|
|
assertEquals(0, allocator.metric().heapArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(0, allocator.metric().heapArenas().get(1).numThreadCaches());
|
|
|
|
assertEquals(0, allocator.metric().directArenas().get(0).numThreadCaches());
|
|
|
|
assertEquals(0, allocator.metric().directArenas().get(1).numThreadCaches());
|
2016-03-14 17:25:43 +01:00
|
|
|
}
|
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
private static ThreadCache createNewThreadCache(final PooledByteBufAllocator allocator)
|
2016-05-11 11:53:31 +02:00
|
|
|
throws InterruptedException {
|
2016-03-14 17:25:43 +01:00
|
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
2016-05-11 11:53:31 +02:00
|
|
|
final CountDownLatch cacheLatch = new CountDownLatch(1);
|
2016-11-21 08:23:42 +01:00
|
|
|
final Thread t = new FastThreadLocalThread(new Runnable() {
|
2016-03-14 17:25:43 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
ByteBuf buf = allocator.newHeapBuffer(1024, 1024);
|
2016-05-11 11:53:31 +02:00
|
|
|
|
|
|
|
// Countdown the latch after we allocated a buffer. At this point the cache must exists.
|
|
|
|
cacheLatch.countDown();
|
|
|
|
|
|
|
|
buf.writeZero(buf.capacity());
|
2016-03-14 17:25:43 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
latch.await();
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.release();
|
|
|
|
|
|
|
|
FastThreadLocal.removeAll();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
t.start();
|
|
|
|
|
2016-05-11 11:53:31 +02:00
|
|
|
// Wait until we allocated a buffer and so be sure the thread was started and the cache exists.
|
|
|
|
cacheLatch.await();
|
2016-03-14 17:25:43 +01:00
|
|
|
|
2016-11-21 08:23:42 +01:00
|
|
|
return new ThreadCache() {
|
|
|
|
@Override
|
|
|
|
public void destroy() throws InterruptedException {
|
|
|
|
latch.countDown();
|
|
|
|
t.join();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private interface ThreadCache {
|
|
|
|
void destroy() throws InterruptedException;
|
2016-03-14 17:25:43 +01:00
|
|
|
}
|
|
|
|
|
2015-10-22 22:09:41 +02:00
|
|
|
@Test
|
|
|
|
public void testConcurrentUsage() throws Throwable {
|
2016-03-14 17:25:43 +01:00
|
|
|
long runningTime = MILLISECONDS.toNanos(SystemPropertyUtil.getLong(
|
2015-10-22 22:09:41 +02:00
|
|
|
"io.netty.buffer.PooledByteBufAllocatorTest.testConcurrentUsageTime", 15000));
|
|
|
|
|
|
|
|
// We use no caches and only one arena to maximize the chance of hitting the race-condition we
|
|
|
|
// had before.
|
|
|
|
ByteBufAllocator allocator = new PooledByteBufAllocator(true, 1, 1, 8192, 11, 0, 0, 0);
|
|
|
|
List<AllocationThread> threads = new ArrayList<AllocationThread>();
|
|
|
|
try {
|
|
|
|
for (int i = 0; i < 512; i++) {
|
|
|
|
AllocationThread thread = new AllocationThread(allocator);
|
|
|
|
thread.start();
|
|
|
|
threads.add(thread);
|
|
|
|
}
|
|
|
|
|
|
|
|
long start = System.nanoTime();
|
|
|
|
while (!isExpired(start, runningTime)) {
|
|
|
|
checkForErrors(threads);
|
|
|
|
Thread.sleep(100);
|
|
|
|
}
|
|
|
|
} finally {
|
2019-03-01 06:47:56 +01:00
|
|
|
// First mark all AllocationThreads to complete their work and then wait until these are complete
|
|
|
|
// and rethrow if there was any error.
|
2015-10-22 22:09:41 +02:00
|
|
|
for (AllocationThread t : threads) {
|
2019-03-01 06:47:56 +01:00
|
|
|
t.markAsFinished();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (AllocationThread t: threads) {
|
|
|
|
t.joinAndCheckForError();
|
2015-10-22 22:09:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isExpired(long start, long expireTime) {
|
|
|
|
return System.nanoTime() - start > expireTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void checkForErrors(List<AllocationThread> threads) throws Throwable {
|
|
|
|
for (AllocationThread t : threads) {
|
|
|
|
if (t.isFinished()) {
|
|
|
|
t.checkForError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final class AllocationThread extends Thread {
|
2016-03-14 17:25:43 +01:00
|
|
|
|
|
|
|
private static final int[] ALLOCATION_SIZES = new int[16 * 1024];
|
|
|
|
static {
|
|
|
|
for (int i = 0; i < ALLOCATION_SIZES.length; i++) {
|
|
|
|
ALLOCATION_SIZES[i] = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 13:26:56 +01:00
|
|
|
private final Queue<ByteBuf> buffers = new ConcurrentLinkedQueue<ByteBuf>();
|
2015-10-22 22:09:41 +02:00
|
|
|
private final ByteBufAllocator allocator;
|
2016-11-23 13:26:56 +01:00
|
|
|
private final AtomicReference<Object> finish = new AtomicReference<Object>();
|
2015-10-22 22:09:41 +02:00
|
|
|
|
2019-01-25 11:58:42 +01:00
|
|
|
AllocationThread(ByteBufAllocator allocator) {
|
2015-10-22 22:09:41 +02:00
|
|
|
this.allocator = allocator;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
int idx = 0;
|
2016-11-23 13:26:56 +01:00
|
|
|
while (finish.get() == null) {
|
2015-10-22 22:09:41 +02:00
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
buffers.add(allocator.directBuffer(
|
|
|
|
ALLOCATION_SIZES[Math.abs(idx++ % ALLOCATION_SIZES.length)],
|
|
|
|
Integer.MAX_VALUE));
|
|
|
|
}
|
|
|
|
releaseBuffers();
|
|
|
|
}
|
|
|
|
} catch (Throwable cause) {
|
2016-11-23 13:26:56 +01:00
|
|
|
finish.set(cause);
|
2015-10-22 22:09:41 +02:00
|
|
|
} finally {
|
|
|
|
releaseBuffers();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void releaseBuffers() {
|
|
|
|
for (;;) {
|
|
|
|
ByteBuf buf = buffers.poll();
|
|
|
|
if (buf == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
buf.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 06:47:56 +01:00
|
|
|
boolean isFinished() {
|
2016-11-23 13:26:56 +01:00
|
|
|
return finish.get() != null;
|
2015-10-22 22:09:41 +02:00
|
|
|
}
|
|
|
|
|
2019-03-01 06:47:56 +01:00
|
|
|
void markAsFinished() {
|
|
|
|
finish.compareAndSet(null, Boolean.TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
void joinAndCheckForError() throws Throwable {
|
2015-10-22 22:09:41 +02:00
|
|
|
try {
|
2016-11-23 13:26:56 +01:00
|
|
|
// Mark as finish if not already done but ensure we not override the previous set error.
|
|
|
|
join();
|
2015-10-22 22:09:41 +02:00
|
|
|
} finally {
|
|
|
|
releaseBuffers();
|
|
|
|
}
|
2016-11-23 13:26:56 +01:00
|
|
|
checkForError();
|
2015-10-22 22:09:41 +02:00
|
|
|
}
|
|
|
|
|
2019-03-01 06:47:56 +01:00
|
|
|
void checkForError() throws Throwable {
|
2016-11-23 13:26:56 +01:00
|
|
|
Object obj = finish.get();
|
|
|
|
if (obj instanceof Throwable) {
|
|
|
|
throw (Throwable) obj;
|
2015-10-22 22:09:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|