a9fda3c8e0
Related: #3166 Motivation: When the recyclable object created at one thread is returned at the other thread, it is stored in a WeakOrderedQueue. The objects stored in the WeakOrderedQueue is added back to the stack by WeakOrderedQueue.transfer() when the owner thread ran out of recyclable objects. However, WeakOrderedQueue.transfer() does not have any mechanism that prevents the stack from growing beyond its maximum capacity. Modifications: - Make WeakOrderedQueue.transfer() increase the capacity of the stack only up to its maximum - Add tests for the cases where the recyclable object is returned at the non-owner thread - Fix a bug where Stack.scavengeSome() does not scavenge the objects when it's the first time it ran out of objects and thus its cursor is null. - Overall clean-up of scavengeSome() and transfer() Result: The capacity of Stack never increases beyond its maximum.
177 lines
5.3 KiB
Java
177 lines
5.3 KiB
Java
/*
|
|
* 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;
|
|
|
|
import org.junit.Test;
|
|
|
|
import java.util.Random;
|
|
|
|
import static org.hamcrest.CoreMatchers.*;
|
|
import static org.junit.Assert.*;
|
|
|
|
public class RecyclerTest {
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void testMultipleRecycle() {
|
|
RecyclableObject object = RecyclableObject.newInstance();
|
|
object.recycle();
|
|
object.recycle();
|
|
}
|
|
|
|
@Test
|
|
public void testRecycle() {
|
|
RecyclableObject object = RecyclableObject.newInstance();
|
|
object.recycle();
|
|
RecyclableObject object2 = RecyclableObject.newInstance();
|
|
assertSame(object, object2);
|
|
object2.recycle();
|
|
}
|
|
|
|
static final class RecyclableObject {
|
|
|
|
private static final Recycler<RecyclableObject> RECYCLER = new Recycler<RecyclableObject>() {
|
|
@Override
|
|
protected RecyclableObject newObject(Handle handle) {
|
|
return new RecyclableObject(handle);
|
|
}
|
|
};
|
|
|
|
private final Recycler.Handle handle;
|
|
|
|
private RecyclableObject(Recycler.Handle handle) {
|
|
this.handle = handle;
|
|
}
|
|
|
|
public static RecyclableObject newInstance() {
|
|
return RECYCLER.get();
|
|
}
|
|
|
|
public void recycle() {
|
|
RECYCLER.recycle(this, handle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
}
|
|
|
|
void testMaxCapacity(int maxCapacity) {
|
|
Recycler<HandledObject> recycler = new Recycler<HandledObject>(maxCapacity) {
|
|
@Override
|
|
protected HandledObject newObject(
|
|
Recycler.Handle handle) {
|
|
return new HandledObject(handle);
|
|
}
|
|
};
|
|
|
|
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++) {
|
|
recycler.recycle(objects[i], objects[i].handle);
|
|
objects[i] = null;
|
|
}
|
|
|
|
assertEquals(maxCapacity, recycler.threadLocalCapacity());
|
|
}
|
|
|
|
@Test
|
|
public void testRecycleAtDifferentThread() throws Exception {
|
|
final Recycler<HandledObject> recycler = new Recycler<HandledObject>(256) {
|
|
@Override
|
|
protected HandledObject newObject(Recycler.Handle handle) {
|
|
return new HandledObject(handle);
|
|
}
|
|
};
|
|
|
|
final HandledObject o = recycler.get();
|
|
final Thread thread = new Thread() {
|
|
@Override
|
|
public void run() {
|
|
recycler.recycle(o, o.handle);
|
|
}
|
|
};
|
|
thread.start();
|
|
thread.join();
|
|
|
|
assertThat(recycler.get(), is(sameInstance(o)));
|
|
}
|
|
|
|
@Test
|
|
public void testMaxCapacityWithRecycleAtDifferentThread() throws Exception {
|
|
final int maxCapacity = 4; // Choose the number smaller than WeakOrderQueue.LINK_CAPACITY
|
|
final Recycler<HandledObject> recycler = new Recycler<HandledObject>(maxCapacity) {
|
|
@Override
|
|
protected HandledObject newObject(Recycler.Handle handle) {
|
|
return new HandledObject(handle);
|
|
}
|
|
};
|
|
|
|
// 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 ++) {
|
|
recycler.recycle(array[i], array[i].handle);
|
|
}
|
|
|
|
final Thread thread = new Thread() {
|
|
@Override
|
|
public void run() {
|
|
for (int i = maxCapacity; i < array.length; i ++) {
|
|
recycler.recycle(array[i], array[i].handle);
|
|
}
|
|
}
|
|
};
|
|
thread.start();
|
|
thread.join();
|
|
|
|
assertThat(recycler.threadLocalCapacity(), is(maxCapacity));
|
|
assertThat(recycler.threadLocalSize(), is(maxCapacity));
|
|
|
|
for (int i = 0; i < array.length; i ++) {
|
|
recycler.get();
|
|
}
|
|
|
|
assertThat(recycler.threadLocalCapacity(), is(maxCapacity));
|
|
assertThat(recycler.threadLocalSize(), is(0));
|
|
}
|
|
|
|
static final class HandledObject {
|
|
Recycler.Handle handle;
|
|
|
|
HandledObject(Recycler.Handle handle) {
|
|
this.handle = handle;
|
|
}
|
|
}
|
|
}
|