diff --git a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java index 21b417003..85ba2e4dd 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -43,6 +43,7 @@ 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; @@ -50,6 +51,7 @@ 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; @@ -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; @@ -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 config = new GenericObjectPoolConfig<>(); + config.setJmxEnabled(false); + try (GenericObjectPool 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, List> operation) throws Exception { + final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); + config.setJmxEnabled(false); + try (GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory(), config)) { + assertEquals(0, pool.getNumIdle(), "should be zero idle"); + pool.setMaxIdle(maxIdleLimit); + pool.setMaxTotal(-1); + + final List 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 pool, CyclicBarrier barrier) throws Exception; + } + + private List getRunnables(final int numThreads, + final CyclicBarrier barrier, + final GenericObjectPool pool, + final PoolOperation operation) { + List 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);