blob: 82a3a5945ee35f888d226dc4dde499a157a8e6be [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.openejb.util;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @version $Rev$ $Date$
*/
public class PoolTest extends TestCase {
private Pool pool;
@Override
protected void setUp() throws Exception {
Bean.instances.set(0);
}
@Override
protected void tearDown() throws Exception {
if (pool != null) pool.stop();
}
public void testStrictBasics() throws Exception {
System.out.println("PoolTest.testStrictBasics");
exerciseStrictPool(1, 0);
exerciseStrictPool(3, 0);
exerciseStrictPool(4, 2);
exerciseStrictPool(5, 5);
}
public void testEmptyPool() throws Exception {
System.out.println("PoolTest.testEmptyPool");
final int max = 4;
final int min = 2;
final Pool<Bean> pool = new Pool<Bean>(max, min, true);
final List<Pool<Bean>.Entry> entries = drain(pool);
// Should have received "max" number of nulls
checkMax(max, entries);
// All entries should be null
for (Pool<Bean>.Entry entry : entries) {
assertNull(entry);
}
// Pool is drained and no permits are available
// Test that an add does not work
assertFalse(pool.add(new Bean()));
// Fill the pool via push
for (int i = 0; i < max; i++) {
pool.push(new Bean());
}
// Drain, check, then discard all
drainCheckPush(max, min, pool);
drainCheckPush(max, min, pool);
drainCheckPush(max, min, pool);
discard(pool, drain(pool));
// Fill the pool one item at a time, check it's integrity
for (int i = 1; i <= max; i++) {
assertTrue("i=" + i + ", max=" + max, pool.add(new Bean()));
List<Pool<Bean>.Entry> list = drain(pool);
checkMax(max, list);
checkMin(Math.min(i, min), list);
checkEntries(i, list);
push(pool, list);
}
}
public void testNonStrictDiscard() throws Exception {
System.out.println("PoolTest.testNonStrictDiscard");
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(0);
builder.setMaxSize(1);
builder.setStrictPooling(false);
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
bean.discard();
}
public Bean create() {
// Should never be called
return new Bean();
}
});
final Pool pool = builder.build().start();
assertNull(pool.pop(0, TimeUnit.MILLISECONDS));
assertNull(pool.pop(0, TimeUnit.MILLISECONDS));
assertNull(pool.pop(0, TimeUnit.MILLISECONDS));
final Bean bean = new Bean();
assertTrue(pool.push(bean));
assertFalse(pool.push(bean));
assertFalse(pool.push(bean));
assertTrue(bean.discarded > 0);
assertNotNull(pool.pop(0, TimeUnit.MILLISECONDS));
assertNull(pool.pop(0, TimeUnit.MILLISECONDS));
assertNull(pool.pop(0, TimeUnit.MILLISECONDS));
}
private <T> void drainCheckPush(int max, int min, Pool<T> pool) throws InterruptedException {
final List<Pool<T>.Entry> list = drain(pool);
checkMax(max, list);
checkMin(min, list);
push(pool, list);
}
private <T> void discard(Pool<T> pool, List<Pool<T>.Entry> list) {
for (Pool<T>.Entry entry : list) {
pool.discard(entry);
}
}
private <T> void push(Pool<T> pool, List<Pool<T>.Entry> list) {
for (Pool<T>.Entry entry : list) {
if (entry != null && entry.get() instanceof Bean) {
Bean bean = (Bean) entry.get();
bean.push();
}
pool.push(entry);
}
}
private void exerciseStrictPool(int max, int min) throws InterruptedException {
Bean.instances.set(0);
Pool<String> pool = new Pool<String>(max, min, true);
// Fill the pool
for (int i = 0; i < max; i++) {
assertTrue(pool.add("a" + i));
}
// Add one past the max
assertFalse(pool.add("extra"));
// Check the contents of the pool
final List<Pool<String>.Entry> entries = drain(pool);
checkMax(max, entries);
checkMin(min, entries);
// Push one back and check pool
pool.push(entries.remove(0));
final List<Pool<String>.Entry> entries2 = drain(pool);
assertEquals(max, entries2.size() + entries.size());
// discard all instances and add new ones
entries.addAll(entries2);
entries2.clear();
final Iterator<Pool<String>.Entry> iterator = entries.iterator();
while (iterator.hasNext()) {
// Attempt two discards, followed by two adds
pool.discard(iterator.next());
if (iterator.hasNext()) {
pool.discard(iterator.next());
pool.add("s" + System.currentTimeMillis());
}
pool.add("s" + System.currentTimeMillis());
}
// Once again check min and max
final List<Pool<String>.Entry> list = drain(pool);
checkMax(max, list);
checkMin(min, list);
}
public void testStrictMultiThreaded() throws Exception {
System.out.println("PoolTest.testStrictMultiThreaded");
final int threadCount = 200;
final Pool pool = new Pool(10, 5, true);
final CountDownLatch startPistol = new CountDownLatch(1);
final CountDownLatch startingLine = new CountDownLatch(10);
final CountDownLatch finishingLine = new CountDownLatch(threadCount);
// Do a business method...
Runnable r = new Runnable() {
public void run() {
startingLine.countDown();
try {
startPistol.await();
Pool.Entry entry = pool.pop(1000, MILLISECONDS);
Thread.sleep(50);
if (entry == null) {
pool.push(new Bean());
} else {
pool.push(entry);
}
} catch (TimeoutException e) {
// Simple timeout while waiting on pop()
} catch (InterruptedException e) {
Thread.interrupted();
}
finishingLine.countDown();
}
};
// -- READY --
// How much ever the no of client invocations the count should be 10 as only 10 instances will be created.
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(r);
t.start();
}
// Wait for the beans to reach the finish line
startingLine.await(1000, TimeUnit.MILLISECONDS);
// -- SET --
assertEquals(0, Bean.instances.get());
// -- GO --
startPistol.countDown(); // go
assertTrue(finishingLine.await(5000, TimeUnit.MILLISECONDS));
// -- DONE --
assertEquals(10, Bean.instances.get());
}
public void testClose() throws Exception {
System.out.println("PoolTest.testClose");
final int min = 4;
final int max = 9;
final int sweepInterval = 200;
final int pause = 1000;
final List<Bean> discarded = new CopyOnWriteArrayList<Bean>();
final CountDownLatch discard = new CountDownLatch(max);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setSweepInterval(new Duration(sweepInterval, TimeUnit.MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
try {
Thread.sleep(pause);
} catch (InterruptedException e) {
Thread.interrupted();
}
bean.discard();
discarded.add(bean);
discard.countDown();
}
public Bean create() {
return new Bean();
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
long start = System.currentTimeMillis();
assertTrue(pool.close(10, TimeUnit.SECONDS));
long time = System.currentTimeMillis() - start;
// All instances should have been removed
assertEquals(max, discarded.size());
// Should have taken at least three seconds
assertTrue(time >= pause);
}
/**
* Tests the idle timeout as well as the Thread pool
* used to invoke the discard/create jobs.
*
*
* @throws Exception exception
*/
public void testIdleTimeout() throws Exception {
System.out.println("PoolTest.testIdleTimeout");
final int min = 4;
final int max = 9;
final int idleTimeout = 1000;
final int sweepInterval = idleTimeout / 4;
final List<Bean> discarded = new CopyOnWriteArrayList<Bean>();
final CountDownLatch discard = new CountDownLatch(max - min);
final CountDownLatch hold = new CountDownLatch(1);
final Pool.Builder<Bean> builder = new Pool.Builder<Bean>();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setExecutor(Executors.newFixedThreadPool(5));
builder.setIdleTimeout(new Duration(idleTimeout, TimeUnit.MILLISECONDS));
builder.setSweepInterval(new Duration(sweepInterval, TimeUnit.MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
bean.discard();
discarded.add(bean);
discard.countDown();
try {
// Executor should have enough threads
// to execute removes on all the
// timed out objects.
hold.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
}
public Bean create() {
// Should never be called
return new Bean();
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
// Wait for the timeout -- items should not expire while in use
Thread.sleep((long) (idleTimeout * 1.2));
push(pool, entries);
}
await(discard, 20, TimeUnit.SECONDS);
// All non-min instances should have been removed
// no more, no less
assertEquals(max - min, discarded.size());
for (Bean bean : discarded) {
final long inactive = bean.discarded - bean.accessed;
// Actual idle time should not be less than our setting
assertTrue("timed out too soon: timeout="+idleTimeout +", idle="+inactive, inactive >= idleTimeout);
// It shouldn't be too much more either
assertTrue("timed out too long", inactive < idleTimeout + (sweepInterval * 2));
}
{ // Pool should only have min number of non-null entries
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(min, entries);
push(pool, entries);
}
// -- DONE --
assertEquals(max, Bean.instances.get());
}
public void testFlush() throws Exception {
System.out.println("PoolTest.testFlush");
final int min = 4;
final int max = 9;
final int sweepInterval = 200;
final List<Bean> discarded = new CopyOnWriteArrayList<Bean>();
final CountDownLatch discard = new CountDownLatch(max);
final CountDownLatch created = new CountDownLatch(min);
final CountDownLatch createInstances = new CountDownLatch(1);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setSweepInterval(new Duration(sweepInterval, TimeUnit.MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
bean.discard();
discarded.add(bean);
discard.countDown();
}
public Bean create() {
try {
createInstances.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
try {
return new Bean();
} finally {
created.countDown();
}
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
pool.flush();
// Wait for the Evictor to come around and sweep out the pool
await(discard, 10, TimeUnit.SECONDS);
// All instances should have been removed
assertEquals(max, discarded.size());
for (Bean bean : discarded) {
final long flushTime = bean.discarded - bean.accessed;
// It shouldn't be too much more either
assertTrue("flush took too long", flushTime < (sweepInterval * 2));
}
// Minimum instances are still being created
// The rest of the pool should be empty (null)
{
final List entries = drain(pool, 100);
// Should have "non-min" number of entries
checkMax(max - min, entries);
// Nothing should be a "min" item as those
// are still being created
checkMin(0, entries);
// And as the pool was just drained all the
// entries we get should be null
checkNull(entries);
push(pool, entries);
}
Bean.instances.set(0);
// Try and trick the pool into adding more "min" items
// Fill the pool as much as we can -- should only let us
// fill to the max factoring in the "min" instances that
// are currently being created
{
final List entries = drain(pool, 100);
// Should reject the instance we try to add
assertFalse(pool.add(new Bean()));
// Empty the pool
discard(pool, entries);
// Now count how many instances it lets us add
Bean.instances.set(0);
while (pool.add(new Bean())) ;
// As the "min" instances are still being created
// it should only let us fill max - min, then + 1
// to account for the instance that gets rejected
// and terminates the while loop
final int expected = max - min + 1;
assertEquals(expected, Bean.instances.getAndSet(0));
}
// Ok, let the "min" instance creation threads continue
createInstances.countDown();
// Wait for the "min" instance creation to complete
assertTrue(created.await(sweepInterval * 10, TimeUnit.MILLISECONDS));
{ // Pool should be full again
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
// -- DONE --
}
public void testMaxAge() throws Exception {
System.out.println("PoolTest.testMaxAge");
final int min = 4;
final int max = 9;
final int maxAge = 1000;
final int sweepInterval = maxAge / 4;
final List<Bean> discarded = new CopyOnWriteArrayList<Bean>();
final CountDownLatch discard = new CountDownLatch(max);
final CountDownLatch created = new CountDownLatch(min);
final CountDownLatch createInstances = new CountDownLatch(1);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setMaxAge(new Duration(maxAge, MILLISECONDS));
builder.setSweepInterval(new Duration(sweepInterval, MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
bean.discard();
discarded.add(bean);
countDown(discard, bean, "discarded");
}
public Bean create() {
try {
createInstances.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
try {
return new Bean();
} finally {
created.countDown();
}
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
// Now wait for the instances in the pool to expire
await(discard, 10, TimeUnit.SECONDS);
// All instances should have been removed
assertEquals(max, discarded.size());
for (Bean bean : discarded) {
final long age = bean.discarded - bean.created;
// Actual idle time should not be less than our setting
assertTrue("died too soon", age >= maxAge);
// It shouldn't be too much more either
assertTrue("lived too long", age < maxAge + (sweepInterval * 2));
}
// Minimum instances are still being created
// The rest of the pool should be empty (null)
{
final List entries = drain(pool, 100);
// Nothing should be a "min" item as those
// are still being created
checkMin(0, entries);
// And as the pool was just drained all the
// entries we get should be null
checkNull(entries);
// Should have "non-min" number of entries
checkMax(max - min, entries);
push(pool, entries);
}
Bean.instances.set(0);
// Try and trick the pool into adding more "min" items
// Fill the pool as much as we can -- should only let us
// fill to the max factoring in the "min" instances that
// are currently being created
{
final List entries = drain(pool, 100);
// Should reject the instance we try to add
assertFalse(pool.add(new Bean()));
// Empty the pool
discard(pool, entries);
// Now count how many instances it lets us add
Bean.instances.set(0);
while (pool.add(new Bean())) ;
// As the "min" instances are still being created
// it should only let us fill max - min, then + 1
// to account for the instance that gets rejected
// and terminates the while loop
final int expected = max - min + 1;
assertEquals(expected, Bean.instances.getAndSet(0));
}
// Ok, let the "min" instance creation threads continue
createInstances.countDown();
// Wait for the "min" instance creation to complete
await(created, 10, TimeUnit.SECONDS);
{ // Pool should be full again
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
// -- DONE --
}
private void countDown(CountDownLatch discarded, Bean o, String event) {
discarded.countDown();
// System.out.format("%1$tH:%1$tM:%1$tS.%1$tL " + event + " %2$s\n", System.currentTimeMillis(), o);
// try {
// Thread.sleep(50);
// } catch (InterruptedException e) {
// Thread.interrupted();
// }
}
/**
* What happens if we fail to create a "min" instance after a flush?
*
* The pool should naturally balance itself out by promoting regular instances
* to "min" instance as things are popped and pushed to and from the pool.
*
* @throws Exception exception
*/
public void testFlushFailedCreation() throws Exception {
System.out.println("PoolTest.testFlushFailedCreation");
final int min = 4;
final int max = 9;
final int poll = 200;
final CountDownLatch discarded = new CountDownLatch(max);
final CountDownLatch created = new CountDownLatch(min);
final CountDownLatch createInstances = new CountDownLatch(1);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setSweepInterval(new Duration(poll, TimeUnit.MILLISECONDS));
builder.setSupplier(new Pool.Supplier() {
public void discard(Object o, Pool.Event reason) {
discarded.countDown();
}
public Object create() {
try {
createInstances.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
try {
throw new RuntimeException();
} finally {
created.countDown();
}
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
pool.flush();
// Wait for the Evictor to come around and sweep out the pool
await(discarded, 10, TimeUnit.SECONDS);
// Minimum instances are still being created
// The rest of the pool should be empty (null)
{
final List entries = drain(pool, 100);
// Should have "non-min" number of entries
checkMax(max - min, entries);
// Nothing should be a "min" item as those
// are still being created
checkMin(0, entries);
// And as the pool was just drained all the
// entries we get should be null
checkNull(entries);
push(pool, entries);
}
Bean.instances.set(0);
// Try and trick the pool into adding more "min" items
// Fill the pool as much as we can -- should only let us
// fill to the max factoring in the "min" instances that
// are currently being created
{
final List entries = drain(pool, 100);
// Should reject the instance we try to add
assertFalse(pool.add(new Bean()));
// Empty the pool
discard(pool, entries);
// Now count how many instances it lets us add
Bean.instances.set(0);
while (pool.add(new Bean())) ;
// As the "min" instances are still being created
// it should only let us fill max - min, then + 1
// to account for the instance that gets rejected
// and terminates the while loop
final int expected = max - min + 1;
assertEquals(expected, Bean.instances.getAndSet(0));
}
// Ok, let the "min" instance creation threads continue
createInstances.countDown();
// Wait for the "min" instance creation to complete
await(created, 10, TimeUnit.SECONDS);
{ // Pool should be full but...
final List entries = drain(pool, 100);
// we failed to create the min instances
checkMin(0, entries);
// the "min" entries should have been freed up
// and we should have all the possible entires
checkMax(max, entries);
// though there should be "min" quantities of nulls
checkEntries(max-min, entries);
// Now when we push these back in, the right number
// of entries should be converted to "min" entries
push(pool, entries);
}
{ // Pool should be full but...
final List entries = drain(pool, 100);
// now we should have the right number of mins
checkMin(min, entries);
// should still have a full pool
checkMax(max, entries);
// though there should still be "min" quantities of nulls
// as we still haven't created any more instances, we just
// converted some of our instances into "min" entries
checkEntries(max-min, entries);
}
// -- DONE --
}
/**
* What happens if we fail to create a "min" instance after a maxAge expiration?
*
* The pool should naturally balance itself out by promoting regular instances
* to "min" instance as things are popped and pushed to and from the pool.
*
* @throws Exception exception
*/
public void testMaxAgeFailedCreation() throws Exception {
System.out.println("PoolTest.testMaxAgeFailedCreation");
final int min = 4;
final int max = 9;
final int maxAge = 5000;
final int poll = 100;
final CountDownLatch discarded = new CountDownLatch(max);
final CountDownLatch created = new CountDownLatch(min);
final CountDownLatch createInstances = new CountDownLatch(1);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setMaxAge(new Duration(maxAge, MILLISECONDS));
builder.setSweepInterval(new Duration(poll, MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean o, Pool.Event reason) {
countDown(discarded, o, "discarded");
}
public Bean create() {
try {
createInstances.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
try {
throw new RuntimeException();
} finally {
created.countDown();
}
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue("bean not added " + i, pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 1000);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
// Now wait for the instances in the pool to expire
await(discarded, maxAge * 4, TimeUnit.MILLISECONDS);
// Minimum instances are still being created
// The rest of the pool should be empty (null)
{
final List entries = drain(pool, 1000);
// Should have "non-min" number of entries
checkMax(max - min, entries);
// Nothing should be a "min" item as those
// are still being created
checkMin(0, entries);
// And as the pool was just drained all the
// entries we get should be null
checkNull(entries);
push(pool, entries);
}
Bean.instances.set(0);
// Try and trick the pool into adding more "min" items
// Fill the pool as much as we can -- should only let us
// fill to the max factoring in the "min" instances that
// are currently being created
{
final List entries = drain(pool, 1000);
// Should reject the instance we try to add
assertFalse(pool.add(new Bean()));
// Empty the pool
discard(pool, entries);
// Now count how many instances it lets us add
Bean.instances.set(0);
while (pool.add(new Bean())) ;
// As the "min" instances are still being created
// it should only let us fill max - min, then + 1
// to account for the instance that gets rejected
// and terminates the while loop
final int expected = max - min + 1;
assertEquals(expected, Bean.instances.getAndSet(0));
}
// Ok, let the "min" instance creation threads continue
createInstances.countDown();
// Wait for the "min" instance creation to complete
await(created, 10, TimeUnit.SECONDS);
{ // Pool should be full but...
final List entries = drain(pool, 1000);
// we failed to create the min instances
checkMin(0, entries);
// the "min" entries should have been freed up
// and we should have all the possible entires
checkMax(max, entries);
// though there should be "min" quantities of nulls
checkEntries(max-min, entries);
// Now when we push these back in, the right number
// of entries should be converted to "min" entries
push(pool, entries);
}
{ // Pool should be full but...
final List entries = drain(pool, 1000);
// now we should have the right number of mins
checkMin(min, entries);
// should still have a full pool
checkMax(max, entries);
// though there should still be "min" quantities of nulls
// as we still haven't created any more instances, we just
// converted some of our instances into "min" entries
checkEntries(max-min, entries);
}
// -- DONE --
}
/**
* When an item is in use, it should still be flushed
* upon return to the pool if the pool was flushed
*
* Active items should still be flushed the moment they
* become inactive (returned to the pool).
*
* @throws Exception
*/
public void testFlushOnReturn() throws Exception {
System.out.println("PoolTest.testFlushOnReturn");
final int min = 4;
final int max = 9;
// Effectively disable sweeping the pool and
// verify that Flush is still enforced by the
// simple act of trying to return a flushed
// item to the pool
final int sweepInterval = Integer.MAX_VALUE;
final CountDownLatch created = new CountDownLatch(min);
final CountDownLatch createInstances = new CountDownLatch(1);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setSweepInterval(new Duration(sweepInterval, TimeUnit.MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
bean.discard();
}
public Bean create() {
try {
createInstances.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
try {
return new Bean();
} finally {
created.countDown();
}
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
{
// Drain pool again, flush, then try and return them
final List<Pool<Bean>.Entry> entries = drain(pool, 100);
checkEntries(max, entries);
// Now call flush
pool.flush();
for (Pool<Bean>.Entry entry : entries) {
assertFalse("entry should not be accepted", pool.push(entry));
}
}
// Minimum instances are still being created
// The rest of the pool should be empty (null)
{
final List entries = drain(pool, 100);
// Should have "non-min" number of entries
checkMax(max - min, entries);
// Nothing should be a "min" item as those
// are still being created
checkMin(0, entries);
// And as the pool was just drained all the
// entries we get should be null
checkNull(entries);
push(pool, entries);
}
Bean.instances.set(0);
// Try and trick the pool into adding more "min" items
// Fill the pool as much as we can -- should only let us
// fill to the max factoring in the "min" instances that
// are currently being created
{
final List entries = drain(pool, 100);
// Should reject the instance we try to add
assertFalse(pool.add(new Bean()));
// Empty the pool
discard(pool, entries);
// Now count how many instances it lets us add
Bean.instances.set(0);
while (pool.add(new Bean())) ;
// As the "min" instances are still being created
// it should only let us fill max - min, then + 1
// to account for the instance that gets rejected
// and terminates the while loop
final int expected = max - min + 1;
assertEquals(expected, Bean.instances.getAndSet(0));
}
// Ok, let the "min" instance creation threads continue
createInstances.countDown();
// Wait for the "min" instance creation to complete
await(created, 10, TimeUnit.SECONDS);
{ // Pool should be full again
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
// -- DONE --
}
/**
* When an item is in use, it should still be expired
* upon return to the pool if the item has lived too long
*
* Active items that have lived too long should still be
* expired the moment they become inactive (returned to the pool).
*
* @throws Exception
*/
public void testMaxAgeOnReturn() throws Exception {
System.out.println("PoolTest.testMaxAgeOnReturn");
final int min = 4;
final int max = 9;
final int maxAge = 200;
final int sweepInterval = Integer.MAX_VALUE;
final CountDownLatch created = new CountDownLatch(min);
final CountDownLatch createInstances = new CountDownLatch(1);
final Pool.Builder builder = new Pool.Builder();
builder.setMinSize(min);
builder.setMaxSize(max);
builder.setMaxAge(new Duration(maxAge, MILLISECONDS));
builder.setSweepInterval(new Duration(sweepInterval, MILLISECONDS));
builder.setSupplier(new Pool.Supplier<Bean>() {
public void discard(Bean bean, Pool.Event reason) {
bean.discard();
}
public Bean create() {
try {
createInstances.await();
} catch (InterruptedException e) {
Thread.interrupted();
}
try {
return new Bean();
} finally {
created.countDown();
}
}
});
final Pool pool = this.pool = builder.build().start();
// Fill pool to max
Bean.instances.set(0);
for (int i = 0; i < max; i++) {
assertTrue(pool.add(new Bean()));
}
{ // Should have a full, non-null pool
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
{
// Drain pool again, flush, then try and return them
final List<Pool<Bean>.Entry> entries = drain(pool, 100);
checkEntries(max, entries);
// Now wait for max age
Thread.sleep(maxAge);
for (Pool<Bean>.Entry entry : entries) {
assertFalse("entry should not be accepted", pool.push(entry));
}
}
// Minimum instances are still being created
// The rest of the pool should be empty (null)
{
final List entries = drain(pool, 100);
// Nothing should be a "min" item as those
// are still being created
checkMin(0, entries);
// And as the pool was just drained all the
// entries we get should be null
checkNull(entries);
// Should have "non-min" number of entries
checkMax(max - min, entries);
push(pool, entries);
}
Bean.instances.set(0);
// Try and trick the pool into adding more "min" items
// Fill the pool as much as we can -- should only let us
// fill to the max factoring in the "min" instances that
// are currently being created
{
final List entries = drain(pool, 100);
checkMax(max - min, entries);
// Should reject the instance we try to add
assertFalse(pool.add(new Bean()));
// Empty the pool
discard(pool, entries);
// Now count how many instances it lets us add
Bean.instances.set(0);
while (pool.add(new Bean())) ;
// As the "min" instances are still being created
// it should only let us fill max - min, then + 1
// to account for the instance that gets rejected
// and terminates the while loop
final int expected = max - min + 1;
assertEquals(expected, Bean.instances.getAndSet(0));
}
// Ok, let the "min" instance creation threads continue
createInstances.countDown();
// Wait for the "min" instance creation to complete
await(created, 10, TimeUnit.SECONDS);
{ // Pool should be full again
final List entries = drain(pool, 100);
checkMin(min, entries);
checkEntries(max, entries);
push(pool, entries);
}
// -- DONE --
}
private void await(CountDownLatch latch, int timeout, TimeUnit seconds) throws InterruptedException {
if (!latch.await(timeout, seconds)) {
// String path = "<dump-failed>";
// try {
// File tmp = File.createTempFile(PoolTest.class.getSimpleName(), "-dump.txt");
// path = HeapDump.to(tmp.getAbsolutePath());
// } catch (IOException e) {
// e.printStackTrace();
// }
fail("latch.await timed out: "+ latch);
}
}
private <T> void checkMax(int max, List<Pool<T>.Entry> entries) {
assertEquals(max, entries.size());
}
private <T> void checkMin(int min, List<Pool<T>.Entry> entries) {
assertEquals(min, getMin(entries).size());
}
private <T> void checkNull(List<Pool<T>.Entry> entries) {
for (Pool<T>.Entry entry : entries) {
assertNull(entry);
}
}
private <T> List<Pool<T>.Entry> getMin(List<Pool<T>.Entry> entries) {
List<Pool<T>.Entry> list = new ArrayList<Pool<T>.Entry>();
for (Pool<T>.Entry entry : entries) {
if (entry != null && entry.hasHardReference()) list.add(entry);
}
return list;
}
private <T> void checkEntries(int expected, List<Pool<T>.Entry> entries) {
int found = 0;
for (Pool<T>.Entry entry : entries) {
if (entry == null) continue;
found++;
assertNotNull(entry.get());
}
assertEquals(expected, found);
}
private <T> List<Pool<T>.Entry> drain(Pool<T> pool) throws InterruptedException {
return drain(pool, 0);
}
private <T> List<Pool<T>.Entry> drain(Pool<T> pool, int timeout) throws InterruptedException {
List<Pool<T>.Entry> entries = new ArrayList<Pool<T>.Entry>();
try {
while (true) {
entries.add(pool.pop(timeout, MILLISECONDS));
}
} catch (TimeoutException e) {
// pool drained
}
return entries;
}
public static class Bean {
public static AtomicInteger instances = new AtomicInteger();
private int count;
private final long created;
private long accessed;
private long discarded;
public Bean() {
created = System.currentTimeMillis();
count = instances.incrementAndGet();
}
public long getCreated() {
return created;
}
public long getAccessed() {
return accessed;
}
public long getDiscarded() {
return discarded;
}
public void push() {
accessed = System.currentTimeMillis();
}
public void discard() {
discarded = System.currentTimeMillis();
}
public int count() {
return count;
}
@Override
public String toString() {
return "Bean{" +
"" + count +
// "count=" + count +
// ", created=" + created +
// ", accessed=" + accessed +
// ", discarded=" + discarded +
'}';
}
}
}