2014-06-13 11:56:35 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2014 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.util;
|
|
|
|
|
2014-12-05 13:09:28 +01:00
|
|
|
import org.junit.Test;
|
|
|
|
|
2014-08-31 02:39:03 +02:00
|
|
|
import java.util.Random;
|
2017-12-13 09:52:53 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
2016-07-11 11:55:09 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2017-12-13 09:52:53 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
2014-08-31 02:39:03 +02:00
|
|
|
|
2014-12-05 13:09:28 +01:00
|
|
|
import static org.junit.Assert.*;
|
2014-06-13 11:56:35 +02:00
|
|
|
|
|
|
|
public class RecyclerTest {
|
|
|
|
|
2016-07-26 20:15:21 +02:00
|
|
|
private static Recycler<HandledObject> newRecycler(int max) {
|
|
|
|
return new Recycler<HandledObject>(max) {
|
|
|
|
@Override
|
|
|
|
protected HandledObject newObject(
|
|
|
|
Recycler.Handle<HandledObject> handle) {
|
|
|
|
return new HandledObject(handle);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-12-13 09:52:53 +01:00
|
|
|
@Test(timeout = 5000L)
|
|
|
|
public void testThreadCanBeCollectedEvenIfHandledObjectIsReferenced() throws Exception {
|
|
|
|
final Recycler<HandledObject> recycler = newRecycler(1024);
|
|
|
|
final AtomicBoolean collected = new AtomicBoolean();
|
2019-01-22 16:07:26 +01:00
|
|
|
final AtomicReference<HandledObject> reference = new AtomicReference<>();
|
2019-01-29 14:06:05 +01:00
|
|
|
Thread thread = new Thread(() -> {
|
|
|
|
HandledObject object = recycler.get();
|
|
|
|
// Store a reference to the HandledObject to ensure it is not collected when the run method finish.
|
|
|
|
reference.set(object);
|
2017-12-13 09:52:53 +01:00
|
|
|
}) {
|
|
|
|
@Override
|
|
|
|
protected void finalize() throws Throwable {
|
|
|
|
super.finalize();
|
|
|
|
collected.set(true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
assertFalse(collected.get());
|
|
|
|
thread.start();
|
|
|
|
thread.join();
|
|
|
|
|
|
|
|
// Null out so it can be collected.
|
|
|
|
thread = null;
|
|
|
|
|
|
|
|
// Loop until the Thread was collected. If we can not collect it the Test will fail due of a timeout.
|
|
|
|
while (!collected.get()) {
|
|
|
|
System.gc();
|
|
|
|
System.runFinalization();
|
|
|
|
Thread.sleep(50);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now call recycle after the Thread was collected to ensure this still works...
|
|
|
|
reference.getAndSet(null).recycle();
|
|
|
|
}
|
|
|
|
|
2014-06-13 11:56:35 +02:00
|
|
|
@Test(expected = IllegalStateException.class)
|
|
|
|
public void testMultipleRecycle() {
|
2016-07-26 20:15:21 +02:00
|
|
|
Recycler<HandledObject> recycler = newRecycler(1024);
|
|
|
|
HandledObject object = recycler.get();
|
2014-06-13 11:56:35 +02:00
|
|
|
object.recycle();
|
|
|
|
object.recycle();
|
|
|
|
}
|
|
|
|
|
2018-08-24 15:05:01 +02:00
|
|
|
@Test(expected = IllegalStateException.class)
|
|
|
|
public void testMultipleRecycleAtDifferentThread() throws InterruptedException {
|
|
|
|
Recycler<HandledObject> recycler = newRecycler(1024);
|
|
|
|
final HandledObject object = recycler.get();
|
2019-01-22 16:07:26 +01:00
|
|
|
final AtomicReference<IllegalStateException> exceptionStore = new AtomicReference<>();
|
2019-01-29 14:06:05 +01:00
|
|
|
final Thread thread1 = new Thread(object::recycle);
|
2018-08-24 15:05:01 +02:00
|
|
|
thread1.start();
|
|
|
|
thread1.join();
|
|
|
|
|
2019-01-29 14:06:05 +01:00
|
|
|
final Thread thread2 = new Thread(() -> {
|
|
|
|
try {
|
|
|
|
object.recycle();
|
|
|
|
} catch (IllegalStateException e) {
|
|
|
|
exceptionStore.set(e);
|
2018-08-24 15:05:01 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
thread2.start();
|
|
|
|
thread2.join();
|
|
|
|
IllegalStateException exception = exceptionStore.get();
|
|
|
|
if (exception != null) {
|
|
|
|
throw exception;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-13 11:56:35 +02:00
|
|
|
@Test
|
|
|
|
public void testRecycle() {
|
2016-07-26 20:15:21 +02:00
|
|
|
Recycler<HandledObject> recycler = newRecycler(1024);
|
|
|
|
HandledObject object = recycler.get();
|
2014-06-13 11:56:35 +02:00
|
|
|
object.recycle();
|
2016-07-26 20:15:21 +02:00
|
|
|
HandledObject object2 = recycler.get();
|
2014-12-05 13:09:28 +01:00
|
|
|
assertSame(object, object2);
|
2014-06-13 11:56:35 +02:00
|
|
|
object2.recycle();
|
|
|
|
}
|
|
|
|
|
2015-08-27 11:08:27 +02:00
|
|
|
@Test
|
|
|
|
public void testRecycleDisable() {
|
2016-07-26 20:15:21 +02:00
|
|
|
Recycler<HandledObject> recycler = newRecycler(-1);
|
|
|
|
HandledObject object = recycler.get();
|
2015-08-27 11:08:27 +02:00
|
|
|
object.recycle();
|
2016-07-26 20:15:21 +02:00
|
|
|
HandledObject object2 = recycler.get();
|
2015-08-27 11:08:27 +02:00
|
|
|
assertNotSame(object, object2);
|
|
|
|
object2.recycle();
|
|
|
|
}
|
|
|
|
|
2014-08-31 02:39:03 +02:00
|
|
|
/**
|
|
|
|
* Test to make sure bug #2848 never happens again
|
|
|
|
* https://github.com/netty/netty/issues/2848
|
|
|
|
*/
|
|
|
|
@Test
|
|
|
|
public void testMaxCapacity() {
|
|
|
|
testMaxCapacity(300);
|
|
|
|
Random rand = new Random();
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
|
|
testMaxCapacity(rand.nextInt(1000) + 256); // 256 - 1256
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-26 20:15:21 +02:00
|
|
|
private static void testMaxCapacity(int maxCapacity) {
|
|
|
|
Recycler<HandledObject> recycler = newRecycler(maxCapacity);
|
2014-08-31 02:39:03 +02:00
|
|
|
HandledObject[] objects = new HandledObject[maxCapacity * 3];
|
|
|
|
for (int i = 0; i < objects.length; i++) {
|
|
|
|
objects[i] = recycler.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < objects.length; i++) {
|
2016-05-17 09:31:58 +02:00
|
|
|
objects[i].recycle();
|
2014-08-31 02:39:03 +02:00
|
|
|
objects[i] = null;
|
|
|
|
}
|
|
|
|
|
2016-07-26 20:15:21 +02:00
|
|
|
assertTrue("The threadLocalCapacity (" + recycler.threadLocalCapacity() + ") must be <= maxCapacity ("
|
|
|
|
+ maxCapacity + ") as we not pool all new handles internally",
|
|
|
|
maxCapacity >= recycler.threadLocalCapacity());
|
2014-12-05 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testRecycleAtDifferentThread() throws Exception {
|
2016-07-25 11:15:56 +02:00
|
|
|
final Recycler<HandledObject> recycler = new Recycler<HandledObject>(256, 10, 2, 10) {
|
2014-12-05 13:09:28 +01:00
|
|
|
@Override
|
2016-05-17 09:31:58 +02:00
|
|
|
protected HandledObject newObject(Recycler.Handle<HandledObject> handle) {
|
2014-12-05 13:09:28 +01:00
|
|
|
return new HandledObject(handle);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
final HandledObject o = recycler.get();
|
2016-07-26 20:15:21 +02:00
|
|
|
final HandledObject o2 = recycler.get();
|
2019-11-01 07:10:42 +01:00
|
|
|
|
2014-12-05 13:09:28 +01:00
|
|
|
final Thread thread = new Thread() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2016-05-17 09:31:58 +02:00
|
|
|
o.recycle();
|
2016-07-26 20:15:21 +02:00
|
|
|
o2.recycle();
|
2014-12-05 13:09:28 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
thread.start();
|
|
|
|
thread.join();
|
|
|
|
|
2019-11-04 11:24:20 +01:00
|
|
|
assertSame(recycler.get(), o);
|
|
|
|
assertNotSame(recycler.get(), o2);
|
2014-12-05 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
public void testMaxCapacityWithRecycleAtDifferentThread() throws Exception {
|
|
|
|
final int maxCapacity = 4; // Choose the number smaller than WeakOrderQueue.LINK_CAPACITY
|
2016-07-26 20:15:21 +02:00
|
|
|
final Recycler<HandledObject> recycler = newRecycler(maxCapacity);
|
2014-12-05 13:09:28 +01:00
|
|
|
|
|
|
|
// Borrow 2 * maxCapacity objects.
|
|
|
|
// Return the half from the same thread.
|
|
|
|
// Return the other half from the different thread.
|
|
|
|
|
|
|
|
final HandledObject[] array = new HandledObject[maxCapacity * 3];
|
|
|
|
for (int i = 0; i < array.length; i ++) {
|
|
|
|
array[i] = recycler.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < maxCapacity; i ++) {
|
2016-05-17 09:31:58 +02:00
|
|
|
array[i].recycle();
|
2014-12-05 13:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
final Thread thread = new Thread() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
for (int i = maxCapacity; i < array.length; i ++) {
|
2016-05-17 09:31:58 +02:00
|
|
|
array[i].recycle();
|
2014-12-05 13:09:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
thread.start();
|
|
|
|
thread.join();
|
|
|
|
|
2016-07-26 20:15:21 +02:00
|
|
|
assertEquals(maxCapacity, recycler.threadLocalCapacity());
|
|
|
|
assertEquals(1, recycler.threadLocalSize());
|
2014-12-05 13:09:28 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < array.length; i ++) {
|
|
|
|
recycler.get();
|
|
|
|
}
|
|
|
|
|
2016-07-26 20:15:21 +02:00
|
|
|
assertEquals(maxCapacity, recycler.threadLocalCapacity());
|
|
|
|
assertEquals(0, recycler.threadLocalSize());
|
2014-08-31 02:39:03 +02:00
|
|
|
}
|
|
|
|
|
2016-07-11 11:55:09 +02:00
|
|
|
@Test
|
|
|
|
public void testDiscardingExceedingElementsWithRecycleAtDifferentThread() throws Exception {
|
|
|
|
final int maxCapacity = 32;
|
|
|
|
final AtomicInteger instancesCount = new AtomicInteger(0);
|
|
|
|
|
|
|
|
final Recycler<HandledObject> recycler = new Recycler<HandledObject>(maxCapacity, 2) {
|
|
|
|
@Override
|
|
|
|
protected HandledObject newObject(Recycler.Handle<HandledObject> handle) {
|
|
|
|
instancesCount.incrementAndGet();
|
|
|
|
return new HandledObject(handle);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Borrow 2 * maxCapacity objects.
|
|
|
|
final HandledObject[] array = new HandledObject[maxCapacity * 2];
|
|
|
|
for (int i = 0; i < array.length; i++) {
|
|
|
|
array[i] = recycler.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(array.length, instancesCount.get());
|
|
|
|
// Reset counter.
|
|
|
|
instancesCount.set(0);
|
|
|
|
|
|
|
|
// Recycle from other thread.
|
|
|
|
final Thread thread = new Thread() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
for (HandledObject object: array) {
|
|
|
|
object.recycle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
thread.start();
|
|
|
|
thread.join();
|
|
|
|
|
|
|
|
assertEquals(0, instancesCount.get());
|
|
|
|
|
|
|
|
// Borrow 2 * maxCapacity objects. Half of them should come from
|
|
|
|
// the recycler queue, the other half should be freshly allocated.
|
|
|
|
for (int i = 0; i < array.length; i++) {
|
|
|
|
recycler.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
// The implementation uses maxCapacity / 2 as limit per WeakOrderQueue
|
2016-07-26 20:15:21 +02:00
|
|
|
assertTrue("The instances count (" + instancesCount.get() + ") must be <= array.length (" + array.length
|
|
|
|
+ ") - maxCapacity (" + maxCapacity + ") / 2 as we not pool all new handles" +
|
|
|
|
" internally", array.length - maxCapacity / 2 <= instancesCount.get());
|
2016-07-11 11:55:09 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 02:39:03 +02:00
|
|
|
static final class HandledObject {
|
|
|
|
Recycler.Handle<HandledObject> handle;
|
|
|
|
|
|
|
|
HandledObject(Recycler.Handle<HandledObject> handle) {
|
|
|
|
this.handle = handle;
|
|
|
|
}
|
2016-05-17 09:31:58 +02:00
|
|
|
|
|
|
|
void recycle() {
|
|
|
|
handle.recycle(this);
|
|
|
|
}
|
2014-08-31 02:39:03 +02:00
|
|
|
}
|
2014-06-13 11:56:35 +02:00
|
|
|
}
|