Skip to content
105 changes: 105 additions & 0 deletions src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

import javax.management.MBeanServer;
import javax.management.ObjectName;
Expand All @@ -71,11 +73,14 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.parallel.Isolated;

/**
*/
@Isolated
class TestGenericObjectPool extends TestBaseObjectPool {
private final class ConcurrentBorrowAndEvictThread extends Thread {
private final boolean borrow;
Expand Down Expand Up @@ -976,6 +981,106 @@ void testAddObject() throws Exception {
assertEquals(0, genericObjectPool.getNumActive(), "should be zero active");
}

/*https://issues.apache.org/jira/browse/POOL-425*/
@Test
@Timeout(value = 60000, unit = TimeUnit.MILLISECONDS)
void testAddObjectRespectsMaxIdleLimit() throws Exception {
final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
config.setJmxEnabled(false);
try (GenericObjectPool<String> pool = new GenericObjectPool<>(new SimpleFactory(), config)) {
assertEquals(0, pool.getNumIdle(), "should be zero idle");
pool.setMaxIdle(1);
pool.addObject();
pool.addObject();
assertEquals(1, pool.getNumIdle(), "should be one idle");

pool.setMaxIdle(-1);
pool.addObject();
pool.addObject();
pool.addObject();
assertEquals(4, pool.getNumIdle(), "should be four idle");
}
}

@RepeatedTest(10)
@Timeout(value = 10, unit = TimeUnit.SECONDS)
void testAddObjectConcurrentCallsRespectsMaxIdleLimit() throws Exception {
final int maxIdleLimit = 5;
final int numThreads = 10;
final CyclicBarrier barrier = new CyclicBarrier(numThreads);

withConcurrentCallsRespectMaxIdle(maxIdleLimit, numThreads, pool ->
getRunnables(numThreads, barrier, pool, (a, b) -> {
b.await(); // Wait for all threads to be ready
a.addObject();
}));
}


@RepeatedTest(10)
@Timeout(value = 10, unit = TimeUnit.SECONDS)
void testReturnObjectConcurrentCallsRespectsMaxIdleLimit() throws Exception {
final int maxIdleLimit = 5;
final int numThreads = 200;
final CyclicBarrier barrier = new CyclicBarrier(numThreads);

withConcurrentCallsRespectMaxIdle(maxIdleLimit, numThreads, pool ->
getRunnables(numThreads, barrier, pool, (a, b) -> {
String pooledObject = a.borrowObject();
b.await(); // Wait for all threads to be ready
a.returnObject(pooledObject);
}));
}

void withConcurrentCallsRespectMaxIdle(int maxIdleLimit, int numThreads, Function<GenericObjectPool<String>, List<Runnable>> operation) throws Exception {
final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
config.setJmxEnabled(false);
try (GenericObjectPool<String> pool = new GenericObjectPool<>(new SimpleFactory(), config)) {
assertEquals(0, pool.getNumIdle(), "should be zero idle");
pool.setMaxIdle(maxIdleLimit);
pool.setMaxTotal(-1);

final List<Runnable> tasks = operation.apply(pool);

final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
try {
tasks.forEach(executorService::submit);
executorService.shutdown();
assertTrue(executorService.awaitTermination(60, TimeUnit.SECONDS),
"Executor did not terminate in time");
} finally {
executorService.shutdownNow(); // Ensure cleanup
}

assertTrue(pool.getNumIdle() <= maxIdleLimit,
"Concurrent addObject() calls should not exceed maxIdle limit of " + maxIdleLimit +
", but found " + pool.getNumIdle() + " idle objects");
}
}

@FunctionalInterface
public interface PoolOperation {
void execute(GenericObjectPool<String> pool, CyclicBarrier barrier) throws Exception;
}

private List<Runnable> getRunnables(final int numThreads,
final CyclicBarrier barrier,
final GenericObjectPool<String> pool,
final PoolOperation operation) {
List<Runnable> tasks = new ArrayList<>();

for (int i = 0; i < numThreads; i++) {
tasks.add(() -> {
try {
operation.execute(pool, barrier);
} catch (Exception e) {
// do nothing
}
});
}
return tasks;
}

@Test
void testAddObjectCanAddToMaxIdle() throws Exception {
genericObjectPool.setMaxTotal(5);
Expand Down
Loading