From bc7e0eb429ada2867a73fed280887c281a287009 Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Sun, 14 Dec 2025 21:21:36 +0000 Subject: [PATCH 1/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) 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..f9c4be5fb 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -976,6 +976,69 @@ 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 { + genericObjectPool.setMaxIdle(1); + genericObjectPool.addObject(); + genericObjectPool.addObject(); + assertEquals(1, genericObjectPool.getNumIdle(), "should be one idle"); + + genericObjectPool.setMaxIdle(-1); + genericObjectPool.addObject(); + genericObjectPool.addObject(); + genericObjectPool.addObject(); + assertEquals(4, genericObjectPool.getNumIdle(), "should be four idle"); + } + + @Test + @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { + final int maxIdleLimit = 5; + final int numThreads = 10; + genericObjectPool.setMaxIdle(maxIdleLimit); + + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(numThreads); + + List tasks = getRunnables(numThreads, startLatch, doneLatch); + + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + tasks.forEach(executorService::submit); + try { + startLatch.countDown(); // Start all threads simultaneously + doneLatch.await(); // Wait for all threads to complete + } finally { + executorService.shutdown(); + assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + } + + assertTrue(genericObjectPool.getNumIdle() <= maxIdleLimit, + "Concurrent addObject() calls should not exceed maxIdle limit of " + maxIdleLimit + + ", but found " + genericObjectPool.getNumIdle() + " idle objects"); + } + + private List getRunnables(final int numThreads, + final CountDownLatch startLatch, + final CountDownLatch doneLatch) { + List tasks = new ArrayList<>(); + + for(int i = 0; i < numThreads; i++) { + tasks.add(() -> { + try { + startLatch.await(); // Wait for all threads to be ready + genericObjectPool.addObject(); + } catch (Exception e) { + Thread.currentThread().interrupt(); // Restore interrupt status + } finally { + doneLatch.countDown(); + } + }); + } + return tasks; + } + @Test void testAddObjectCanAddToMaxIdle() throws Exception { genericObjectPool.setMaxTotal(5); From 2b0f30a3593894abc0b7f536c6eada0f1bb69665 Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Sun, 14 Dec 2025 21:53:54 +0000 Subject: [PATCH 2/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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 f9c4be5fb..b47efd94e 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -71,6 +71,7 @@ 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; @@ -980,6 +981,8 @@ void testAddObject() throws Exception { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testAddObjectRespectsMaxIdleLimit() throws Exception { + genericObjectPool.clear(); + assertEquals(0, genericObjectPool.getNumIdle(), "should be zero idle"); genericObjectPool.setMaxIdle(1); genericObjectPool.addObject(); genericObjectPool.addObject(); @@ -992,23 +995,22 @@ void testAddObjectRespectsMaxIdleLimit() throws Exception { assertEquals(4, genericObjectPool.getNumIdle(), "should be four idle"); } - @Test + @RepeatedTest(10) @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { + genericObjectPool.clear(); + assertEquals(0, genericObjectPool.getNumIdle(), "should be zero idle"); final int maxIdleLimit = 5; final int numThreads = 10; genericObjectPool.setMaxIdle(maxIdleLimit); final CountDownLatch startLatch = new CountDownLatch(1); - final CountDownLatch doneLatch = new CountDownLatch(numThreads); - - List tasks = getRunnables(numThreads, startLatch, doneLatch); + List tasks = getRunnables(numThreads, startLatch); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); tasks.forEach(executorService::submit); try { startLatch.countDown(); // Start all threads simultaneously - doneLatch.await(); // Wait for all threads to complete } finally { executorService.shutdown(); assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); @@ -1020,8 +1022,7 @@ void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { } private List getRunnables(final int numThreads, - final CountDownLatch startLatch, - final CountDownLatch doneLatch) { + final CountDownLatch startLatch) { List tasks = new ArrayList<>(); for(int i = 0; i < numThreads; i++) { @@ -1030,9 +1031,7 @@ private List getRunnables(final int numThreads, startLatch.await(); // Wait for all threads to be ready genericObjectPool.addObject(); } catch (Exception e) { - Thread.currentThread().interrupt(); // Restore interrupt status - } finally { - doneLatch.countDown(); + // do nothing } }); } From a91f9a42f49324c9033c6838075ab150c3a360d2 Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Thu, 25 Dec 2025 12:07:51 +0000 Subject: [PATCH 3/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) 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 b47efd94e..59e7ce780 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -1021,6 +1021,33 @@ void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { ", but found " + genericObjectPool.getNumIdle() + " idle objects"); } + @RepeatedTest(10) + @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + void testReturnObjectRespectsMaxIdleLimit() throws Exception { + genericObjectPool.clear(); + assertEquals(0, genericObjectPool.getNumIdle(), "should be zero idle"); + final int maxIdleLimit = 1; + final int numThreads = 100; + final int maxTotal = -1; + + genericObjectPool.setMaxTotal(maxTotal); + genericObjectPool.setMaxIdle(maxIdleLimit); + + final CountDownLatch startLatch = new CountDownLatch(1); + List tasks = getReturnRunnables(numThreads, startLatch); + + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + tasks.forEach(executorService::submit); + try { + startLatch.countDown(); // Start all threads simultaneously + } finally { + executorService.shutdown(); + assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + } + + assertEquals(maxIdleLimit, genericObjectPool.getNumIdle(), " Should not be more than " + maxIdleLimit + " idle objects"); + } + private List getRunnables(final int numThreads, final CountDownLatch startLatch) { List tasks = new ArrayList<>(); @@ -1038,6 +1065,24 @@ private List getRunnables(final int numThreads, return tasks; } + private List getReturnRunnables(final int numThreads, + final CountDownLatch startLatch) { + List tasks = new ArrayList<>(); + + for(int i = 0; i < numThreads; i++) { + tasks.add(() -> { + try { + String pooledObject = genericObjectPool.borrowObject(); + startLatch.await(); // Wait for all threads to be ready + genericObjectPool.returnObject(pooledObject); + } catch (Exception e) { + // do nothing + } + }); + } + return tasks; + } + @Test void testAddObjectCanAddToMaxIdle() throws Exception { genericObjectPool.setMaxTotal(5); From b322ff7f2560b403c487c1cea915237c3b1cf92b Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Thu, 25 Dec 2025 19:41:11 +0000 Subject: [PATCH 4/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 112 ++++++++++-------- 1 file changed, 60 insertions(+), 52 deletions(-) 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 59e7ce780..736ec5fa2 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -74,9 +74,11 @@ 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; @@ -981,82 +983,87 @@ void testAddObject() throws Exception { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testAddObjectRespectsMaxIdleLimit() throws Exception { - genericObjectPool.clear(); - assertEquals(0, genericObjectPool.getNumIdle(), "should be zero idle"); - genericObjectPool.setMaxIdle(1); - genericObjectPool.addObject(); - genericObjectPool.addObject(); - assertEquals(1, genericObjectPool.getNumIdle(), "should be one idle"); + try (GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory())) { + assertEquals(0, pool.getNumIdle(), "should be zero idle"); + pool.setMaxIdle(1); + pool.addObject(); + pool.addObject(); + assertEquals(1, pool.getNumIdle(), "should be one idle"); - genericObjectPool.setMaxIdle(-1); - genericObjectPool.addObject(); - genericObjectPool.addObject(); - genericObjectPool.addObject(); - assertEquals(4, genericObjectPool.getNumIdle(), "should be four idle"); + pool.setMaxIdle(-1); + pool.addObject(); + pool.addObject(); + pool.addObject(); + assertEquals(4, pool.getNumIdle(), "should be four idle"); + } } @RepeatedTest(10) @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { - genericObjectPool.clear(); - assertEquals(0, genericObjectPool.getNumIdle(), "should be zero idle"); - final int maxIdleLimit = 5; - final int numThreads = 10; - genericObjectPool.setMaxIdle(maxIdleLimit); + try (GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory())) { + assertEquals(0, pool.getNumIdle(), "should be zero idle"); + final int maxIdleLimit = 5; + final int numThreads = 10; + pool.setMaxIdle(maxIdleLimit); - final CountDownLatch startLatch = new CountDownLatch(1); - List tasks = getRunnables(numThreads, startLatch); + final CountDownLatch startLatch = new CountDownLatch(1); + List tasks = getRunnables(numThreads, startLatch, pool); - ExecutorService executorService = Executors.newFixedThreadPool(numThreads); - tasks.forEach(executorService::submit); - try { - startLatch.countDown(); // Start all threads simultaneously - } finally { - executorService.shutdown(); - assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); - } + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + tasks.forEach(executorService::submit); + try { + startLatch.countDown(); // Start all threads simultaneously + } finally { + executorService.shutdown(); + assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + } - assertTrue(genericObjectPool.getNumIdle() <= maxIdleLimit, - "Concurrent addObject() calls should not exceed maxIdle limit of " + maxIdleLimit + - ", but found " + genericObjectPool.getNumIdle() + " idle objects"); + assertTrue(pool.getNumIdle() <= maxIdleLimit, + "Concurrent addObject() calls should not exceed maxIdle limit of " + maxIdleLimit + + ", but found " + pool.getNumIdle() + " idle objects"); + } } @RepeatedTest(10) @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testReturnObjectRespectsMaxIdleLimit() throws Exception { - genericObjectPool.clear(); - assertEquals(0, genericObjectPool.getNumIdle(), "should be zero idle"); - final int maxIdleLimit = 1; - final int numThreads = 100; - final int maxTotal = -1; + try (final GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory())) { + assertEquals(0, pool.getNumIdle(), "should be zero idle"); + final int maxIdleLimit = 1; + final int numThreads = 2; + final int maxTotal = -1; - genericObjectPool.setMaxTotal(maxTotal); - genericObjectPool.setMaxIdle(maxIdleLimit); + pool.setMaxTotal(maxTotal); + pool.setMaxIdle(maxIdleLimit); - final CountDownLatch startLatch = new CountDownLatch(1); - List tasks = getReturnRunnables(numThreads, startLatch); + final CountDownLatch startLatch = new CountDownLatch(1); + List tasks = getReturnRunnables(numThreads, startLatch, pool); - ExecutorService executorService = Executors.newFixedThreadPool(numThreads); - tasks.forEach(executorService::submit); - try { - startLatch.countDown(); // Start all threads simultaneously - } finally { - executorService.shutdown(); - assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); - } + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + tasks.forEach(executorService::submit); + try { + startLatch.countDown(); // Start all threads simultaneously + } finally { + executorService.shutdown(); + assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + } - assertEquals(maxIdleLimit, genericObjectPool.getNumIdle(), " Should not be more than " + maxIdleLimit + " idle objects"); + assertEquals(maxIdleLimit, pool.getNumIdle(), + " Should not be more than " + maxIdleLimit + " idle objects"); + } } private List getRunnables(final int numThreads, - final CountDownLatch startLatch) { + final CountDownLatch startLatch, + final GenericObjectPool pool) { List tasks = new ArrayList<>(); for(int i = 0; i < numThreads; i++) { tasks.add(() -> { try { startLatch.await(); // Wait for all threads to be ready - genericObjectPool.addObject(); + pool.addObject(); } catch (Exception e) { // do nothing } @@ -1066,15 +1073,16 @@ private List getRunnables(final int numThreads, } private List getReturnRunnables(final int numThreads, - final CountDownLatch startLatch) { + final CountDownLatch startLatch, + final GenericObjectPool pool) { List tasks = new ArrayList<>(); for(int i = 0; i < numThreads; i++) { tasks.add(() -> { try { - String pooledObject = genericObjectPool.borrowObject(); + String pooledObject = pool.borrowObject(); startLatch.await(); // Wait for all threads to be ready - genericObjectPool.returnObject(pooledObject); + pool.returnObject(pooledObject); } catch (Exception e) { // do nothing } From 8b00d2c4d3fb8f027587767c4e9343e8e99ff96a Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Thu, 25 Dec 2025 20:43:49 +0000 Subject: [PATCH 5/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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 736ec5fa2..525fb2d39 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -983,7 +983,9 @@ void testAddObject() throws Exception { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testAddObjectRespectsMaxIdleLimit() throws Exception { - try (GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory())) { + 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(); @@ -1001,11 +1003,14 @@ void testAddObjectRespectsMaxIdleLimit() throws Exception { @RepeatedTest(10) @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { - try (GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory())) { + final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); + config.setJmxEnabled(false); + try (GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory(), config)) { assertEquals(0, pool.getNumIdle(), "should be zero idle"); final int maxIdleLimit = 5; final int numThreads = 10; pool.setMaxIdle(maxIdleLimit); + pool.setMaxTotal(-1); final CountDownLatch startLatch = new CountDownLatch(1); List tasks = getRunnables(numThreads, startLatch, pool); @@ -1028,13 +1033,14 @@ void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { @RepeatedTest(10) @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testReturnObjectRespectsMaxIdleLimit() throws Exception { - try (final GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory())) { + final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); + config.setJmxEnabled(false); + try (final GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory(), config)) { assertEquals(0, pool.getNumIdle(), "should be zero idle"); - final int maxIdleLimit = 1; - final int numThreads = 2; - final int maxTotal = -1; + final int maxIdleLimit = 5; + final int numThreads = 100; - pool.setMaxTotal(maxTotal); + pool.setMaxTotal(-1); pool.setMaxIdle(maxIdleLimit); final CountDownLatch startLatch = new CountDownLatch(1); From 470ab2da771af3bb0494fd77c4f0464311567383 Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Thu, 25 Dec 2025 21:09:41 +0000 Subject: [PATCH 6/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) 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 525fb2d39..58aba90f8 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; @@ -1012,18 +1013,21 @@ void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { pool.setMaxIdle(maxIdleLimit); pool.setMaxTotal(-1); - final CountDownLatch startLatch = new CountDownLatch(1); - List tasks = getRunnables(numThreads, startLatch, pool); + final CyclicBarrier barrier = new CyclicBarrier(numThreads); + List tasks = getRunnables(numThreads, barrier, pool); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); - tasks.forEach(executorService::submit); try { - startLatch.countDown(); // Start all threads simultaneously - } finally { + tasks.forEach(executorService::submit); executorService.shutdown(); - assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + assertTrue(executorService.awaitTermination(60, TimeUnit.SECONDS), + "Executor did not terminate in time"); + } finally { + executorService.shutdownNow(); // Ensure cleanup } + assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + assertTrue(pool.getNumIdle() <= maxIdleLimit, "Concurrent addObject() calls should not exceed maxIdle limit of " + maxIdleLimit + ", but found " + pool.getNumIdle() + " idle objects"); @@ -1037,22 +1041,23 @@ void testReturnObjectRespectsMaxIdleLimit() throws Exception { config.setJmxEnabled(false); try (final GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory(), config)) { assertEquals(0, pool.getNumIdle(), "should be zero idle"); - final int maxIdleLimit = 5; - final int numThreads = 100; + final int maxIdleLimit = 10; + final int numThreads = 200; pool.setMaxTotal(-1); pool.setMaxIdle(maxIdleLimit); - final CountDownLatch startLatch = new CountDownLatch(1); - List tasks = getReturnRunnables(numThreads, startLatch, pool); + final CyclicBarrier barrier = new CyclicBarrier(numThreads); + List tasks = getReturnRunnables(numThreads, barrier, pool); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); - tasks.forEach(executorService::submit); try { - startLatch.countDown(); // Start all threads simultaneously - } finally { + tasks.forEach(executorService::submit); executorService.shutdown(); - assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); + assertTrue(executorService.awaitTermination(60, TimeUnit.SECONDS), + "Executor did not terminate in time"); + } finally { + executorService.shutdownNow(); // Ensure cleanup } assertEquals(maxIdleLimit, pool.getNumIdle(), @@ -1061,14 +1066,14 @@ void testReturnObjectRespectsMaxIdleLimit() throws Exception { } private List getRunnables(final int numThreads, - final CountDownLatch startLatch, + final CyclicBarrier barrier, final GenericObjectPool pool) { List tasks = new ArrayList<>(); for(int i = 0; i < numThreads; i++) { tasks.add(() -> { try { - startLatch.await(); // Wait for all threads to be ready + barrier.await(); // Wait for all threads to be ready pool.addObject(); } catch (Exception e) { // do nothing @@ -1079,7 +1084,7 @@ private List getRunnables(final int numThreads, } private List getReturnRunnables(final int numThreads, - final CountDownLatch startLatch, + final CyclicBarrier barrier, final GenericObjectPool pool) { List tasks = new ArrayList<>(); @@ -1087,7 +1092,7 @@ private List getReturnRunnables(final int numThreads, tasks.add(() -> { try { String pooledObject = pool.borrowObject(); - startLatch.await(); // Wait for all threads to be ready + barrier.await(); // Wait for all threads to be ready pool.returnObject(pooledObject); } catch (Exception e) { // do nothing From 3f93386ff7dd91919e591e9b142155131a286c6b Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Thu, 25 Dec 2025 21:10:32 +0000 Subject: [PATCH 7/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../org/apache/commons/pool2/impl/TestGenericObjectPool.java | 2 -- 1 file changed, 2 deletions(-) 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 58aba90f8..b4701f10f 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -1026,8 +1026,6 @@ void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { executorService.shutdownNow(); // Ensure cleanup } - assertTrue(executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)); - assertTrue(pool.getNumIdle() <= maxIdleLimit, "Concurrent addObject() calls should not exceed maxIdle limit of " + maxIdleLimit + ", but found " + pool.getNumIdle() + " idle objects"); From 40f4ca01640a8f2c07b348a21b15c84048f49072 Mon Sep 17 00:00:00 2001 From: "raju.gupta" Date: Thu, 25 Dec 2025 21:36:12 +0000 Subject: [PATCH 8/8] POOL-425 Add failing tests to reproduce POOL-426 --- .../pool2/impl/TestGenericObjectPool.java | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) 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 b4701f10f..967786626 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java @@ -51,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; @@ -1002,21 +1003,37 @@ void testAddObjectRespectsMaxIdleLimit() throws Exception { } @RepeatedTest(10) - @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) - void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { + @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)); + } + + + @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 -> getReturnRunnables(numThreads, barrier, pool)); + } + + 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"); - final int maxIdleLimit = 5; - final int numThreads = 10; pool.setMaxIdle(maxIdleLimit); pool.setMaxTotal(-1); - final CyclicBarrier barrier = new CyclicBarrier(numThreads); - List tasks = getRunnables(numThreads, barrier, pool); + final List tasks = operation.apply(pool); - ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + final ExecutorService executorService = Executors.newFixedThreadPool(numThreads); try { tasks.forEach(executorService::submit); executorService.shutdown(); @@ -1032,36 +1049,6 @@ void testAddObjectConcurrentCallsRespectsMaxIdle() throws Exception { } } - @RepeatedTest(10) - @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) - void testReturnObjectRespectsMaxIdleLimit() throws Exception { - final GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); - config.setJmxEnabled(false); - try (final GenericObjectPool pool = new GenericObjectPool<>(new SimpleFactory(), config)) { - assertEquals(0, pool.getNumIdle(), "should be zero idle"); - final int maxIdleLimit = 10; - final int numThreads = 200; - - pool.setMaxTotal(-1); - pool.setMaxIdle(maxIdleLimit); - - final CyclicBarrier barrier = new CyclicBarrier(numThreads); - List tasks = getReturnRunnables(numThreads, barrier, pool); - - 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 - } - - assertEquals(maxIdleLimit, pool.getNumIdle(), - " Should not be more than " + maxIdleLimit + " idle objects"); - } - } private List getRunnables(final int numThreads, final CyclicBarrier barrier,