This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch POOL_2_X in repository https://gitbox.apache.org/repos/asf/commons-pool.git
commit 2e3c48cff36af1791938d8ca9b11e9b929a566dc Author: Gary Gregory <[email protected]> AuthorDate: Fri Nov 21 20:31:51 2025 +0000 Sort memebers --- .../commons/pool2/impl/GenericKeyedObjectPool.java | 80 ++++----- .../pool2/impl/GenericKeyedObjectPoolConfig.java | 40 ++--- .../pool2/impl/TestGenericKeyedObjectPool.java | 198 ++++++++++----------- .../commons/pool2/impl/TestGenericObjectPool.java | 170 +++++++++--------- 4 files changed, 244 insertions(+), 244 deletions(-) diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java index fa2923dd..993bfc85 100644 --- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java +++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java @@ -1275,32 +1275,6 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T> return Math.min(this.minIdlePerKey, maxIdlePerKeySave); } - /** - * Gets whether to call {@link #reuseCapacity()} when returning objects to the pool. - * When true, the pool will check if there are threads waiting to borrow objects - * and attempt to reuse the capacity freed by the return operation. - * - * @return {@code true} if capacity reuse is enabled on return, {@code false} otherwise - * @see #setReuseCapacityOnReturn(boolean) - * @since 2.13.0 - */ - public boolean getReuseCapacityOnReturn() { - return reuseCapacityOnReturn; - } - - /** - * Gets whether to call {@link #reuseCapacity()} during pool maintenance (eviction). - * When true, the pool will attempt to reuse freed capacity at the end of each - * eviction run. - * - * @return {@code true} if capacity reuse is enabled during maintenance, {@code false} otherwise - * @see #setReuseCapacityOnMaintenance(boolean) - * @since 2.13.0 - */ - public boolean getReuseCapacityOnMaintenance() { - return reuseCapacityOnMaintenance; - } - @Override public int getNumActive() { return numTotal.get() - getNumIdle(); @@ -1384,6 +1358,32 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T> return result; } + /** + * Gets whether to call {@link #reuseCapacity()} during pool maintenance (eviction). + * When true, the pool will attempt to reuse freed capacity at the end of each + * eviction run. + * + * @return {@code true} if capacity reuse is enabled during maintenance, {@code false} otherwise + * @see #setReuseCapacityOnMaintenance(boolean) + * @since 2.13.0 + */ + public boolean getReuseCapacityOnMaintenance() { + return reuseCapacityOnMaintenance; + } + + /** + * Gets whether to call {@link #reuseCapacity()} when returning objects to the pool. + * When true, the pool will check if there are threads waiting to borrow objects + * and attempt to reuse the capacity freed by the return operation. + * + * @return {@code true} if capacity reuse is enabled on return, {@code false} otherwise + * @see #setReuseCapacityOnReturn(boolean) + * @since 2.13.0 + */ + public boolean getReuseCapacityOnReturn() { + return reuseCapacityOnReturn; + } + @Override String getStatsString() { // Simply listed in AB order. @@ -1790,20 +1790,6 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T> this.minIdlePerKey = minIdlePerKey; } - /** - * Sets whether to call {@link #reuseCapacity()} when returning objects to the pool. - * When enabled, the pool will check if there are threads waiting to borrow objects - * and attempt to reuse capacity (across pools) on return. - * - * @param reuseCapacityOnReturn {@code true} to enable capacity reuse on return, - * {@code false} to disable - * @see #getReuseCapacityOnReturn() - * @since 2.13.0 - */ - public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) { - this.reuseCapacityOnReturn = reuseCapacityOnReturn; - } - /** * Sets whether to call {@link #reuseCapacity()} during pool maintenance (eviction). * When enabled, the pool will attempt to reuse capacity at the end of each @@ -1818,6 +1804,20 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T> this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance; } + /** + * Sets whether to call {@link #reuseCapacity()} when returning objects to the pool. + * When enabled, the pool will check if there are threads waiting to borrow objects + * and attempt to reuse capacity (across pools) on return. + * + * @param reuseCapacityOnReturn {@code true} to enable capacity reuse on return, + * {@code false} to disable + * @see #getReuseCapacityOnReturn() + * @since 2.13.0 + */ + public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) { + this.reuseCapacityOnReturn = reuseCapacityOnReturn; + } + @Override protected void toStringAppendFields(final StringBuilder builder) { super.toStringAppendFields(builder); diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java index 0523104e..5eb16948 100644 --- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java +++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java @@ -155,31 +155,31 @@ public class GenericKeyedObjectPoolConfig<T> extends BaseObjectPoolConfig<T> { } /** - * Gets the value for the {@code reuseCapacityOnReturn} configuration attribute + * Gets the value for the {@code reuseCapacityOnMaintenance} configuration attribute * for pools created with this configuration instance. * - * @return The current setting of {@code reuseCapacityOnReturn} for this + * @return The current setting of {@code reuseCapacityOnMaintenance} for this * configuration instance * - * @see GenericKeyedObjectPool#getReuseCapacityOnReturn() + * @see GenericKeyedObjectPool#getReuseCapacityOnMaintenance() * @since 2.13.0 */ - public boolean getReuseCapacityOnReturn() { - return reuseCapacityOnReturn; + public boolean getReuseCapacityOnMaintenance() { + return reuseCapacityOnMaintenance; } /** - * Gets the value for the {@code reuseCapacityOnMaintenance} configuration attribute + * Gets the value for the {@code reuseCapacityOnReturn} configuration attribute * for pools created with this configuration instance. * - * @return The current setting of {@code reuseCapacityOnMaintenance} for this + * @return The current setting of {@code reuseCapacityOnReturn} for this * configuration instance * - * @see GenericKeyedObjectPool#getReuseCapacityOnMaintenance() + * @see GenericKeyedObjectPool#getReuseCapacityOnReturn() * @since 2.13.0 */ - public boolean getReuseCapacityOnMaintenance() { - return reuseCapacityOnMaintenance; + public boolean getReuseCapacityOnReturn() { + return reuseCapacityOnReturn; } /** @@ -235,31 +235,31 @@ public class GenericKeyedObjectPoolConfig<T> extends BaseObjectPoolConfig<T> { } /** - * Sets the value for the {@code reuseCapacityOnReturn} configuration attribute for + * Sets the value for the {@code reuseCapacityOnMaintenance} configuration attribute for * pools created with this configuration instance. * - * @param reuseCapacityOnReturn The new setting of {@code reuseCapacityOnReturn} + * @param reuseCapacityOnMaintenance The new setting of {@code reuseCapacityOnMaintenance} * for this configuration instance * - * @see GenericKeyedObjectPool#setReuseCapacityOnReturn(boolean) + * @see GenericKeyedObjectPool#setReuseCapacityOnMaintenance(boolean) * @since 2.13.0 */ - public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) { - this.reuseCapacityOnReturn = reuseCapacityOnReturn; + public void setReuseCapacityOnMaintenance(final boolean reuseCapacityOnMaintenance) { + this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance; } /** - * Sets the value for the {@code reuseCapacityOnMaintenance} configuration attribute for + * Sets the value for the {@code reuseCapacityOnReturn} configuration attribute for * pools created with this configuration instance. * - * @param reuseCapacityOnMaintenance The new setting of {@code reuseCapacityOnMaintenance} + * @param reuseCapacityOnReturn The new setting of {@code reuseCapacityOnReturn} * for this configuration instance * - * @see GenericKeyedObjectPool#setReuseCapacityOnMaintenance(boolean) + * @see GenericKeyedObjectPool#setReuseCapacityOnReturn(boolean) * @since 2.13.0 */ - public void setReuseCapacityOnMaintenance(final boolean reuseCapacityOnMaintenance) { - this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance; + public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) { + this.reuseCapacityOnReturn = reuseCapacityOnReturn; } @Override diff --git a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java index 02224c02..353276bf 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java @@ -2414,6 +2414,104 @@ public class TestGenericKeyedObjectPool extends AbstractTestKeyedObjectPool { } } + /** + * Verify that when reuseCapacityOnMaintenance is true, eviction triggers reuseCapacity + * and when reuseCapacityOnReturn is false, returning an object does not trigger reuseCapacity. + * JIRA: POOL-350 + */ + @Test + @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS) + void testReuseCapacityOnMaintenanceBehavior() throws Exception { + gkoPool.setMaxTotalPerKey(2); + gkoPool.setMaxTotal(4); + gkoPool.setBlockWhenExhausted(true); + gkoPool.setMaxWait(Duration.ofSeconds(5)); + gkoPool.setReuseCapacityOnReturn(false); + gkoPool.setReuseCapacityOnMaintenance(true); + + // Create a waiter on key1 + final Thread waiter = new Thread(new SimpleTestThread<>(gkoPool, "key1")); + + // Exhaust capacity + final String obj1 = gkoPool.borrowObject("key2"); + final String obj2 = gkoPool.borrowObject("key2"); + final String obj3 = gkoPool.borrowObject("key3"); + final String obj4 = gkoPool.borrowObject("key3"); + + Thread.sleep(100); + + // Launch the waiter - it will be blocked + waiter.start(); + Thread.sleep(100); + + + // Return one object to free capacity + gkoPool.returnObject("key2", obj1); + + // Even with capacity available, waiter should still be blocked + // because reuseCapacityOnReturn is false + Thread.sleep(100); + assertTrue(waiter.isAlive()); + + // Call evict - with reuseCapacityOnMaintenance=true, this should serve the waiter + gkoPool.evict(); + Thread.sleep(100); + + // Waiter should have been served + assertFalse(waiter.isAlive()); + + // Clean up + gkoPool.returnObject("key2", obj2); + gkoPool.returnObject("key3", obj3); + gkoPool.returnObject("key3", obj4); + } + + @Test + void testReuseCapacityOnMaintenanceConfig() throws Exception { + // Test default value + assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE, gkoPool.getReuseCapacityOnMaintenance()); + assertFalse(gkoPool.getReuseCapacityOnMaintenance()); + + // Test setting via config object + final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>(); + config.setReuseCapacityOnMaintenance(true); + assertEquals(true, config.getReuseCapacityOnMaintenance()); + + try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) { + assertEquals(true, pool.getReuseCapacityOnMaintenance()); + } + + // Test setter + gkoPool.setReuseCapacityOnMaintenance(true); + assertEquals(true, gkoPool.getReuseCapacityOnMaintenance()); + + gkoPool.setReuseCapacityOnMaintenance(false); + assertEquals(false, gkoPool.getReuseCapacityOnMaintenance()); + } + + @Test + void testReuseCapacityOnReturnConfig() throws Exception { + // Test default value + assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_RETURN, gkoPool.getReuseCapacityOnReturn()); + assertTrue(gkoPool.getReuseCapacityOnReturn()); + + // Test setting via config object + final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>(); + config.setReuseCapacityOnReturn(false); + assertEquals(false, config.getReuseCapacityOnReturn()); + + try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) { + assertEquals(false, pool.getReuseCapacityOnReturn()); + } + + // Test setter + gkoPool.setReuseCapacityOnReturn(false); + assertEquals(false, gkoPool.getReuseCapacityOnReturn()); + + gkoPool.setReuseCapacityOnReturn(true); + assertEquals(true, gkoPool.getReuseCapacityOnReturn()); + } + @Test @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS) void testSettersAndGetters() { @@ -2552,6 +2650,7 @@ public class TestGenericKeyedObjectPool extends AbstractTestKeyedObjectPool { } } + // POOL-276 @Test void testValidationOnCreateOnly() throws Exception { @@ -2660,104 +2759,5 @@ public class TestGenericKeyedObjectPool extends AbstractTestKeyedObjectPool { // Check thread was interrupted assertTrue(wtt.thrown instanceof InterruptedException); } - - - @Test - void testReuseCapacityOnReturnConfig() throws Exception { - // Test default value - assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_RETURN, gkoPool.getReuseCapacityOnReturn()); - assertTrue(gkoPool.getReuseCapacityOnReturn()); - - // Test setting via config object - final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>(); - config.setReuseCapacityOnReturn(false); - assertEquals(false, config.getReuseCapacityOnReturn()); - - try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) { - assertEquals(false, pool.getReuseCapacityOnReturn()); - } - - // Test setter - gkoPool.setReuseCapacityOnReturn(false); - assertEquals(false, gkoPool.getReuseCapacityOnReturn()); - - gkoPool.setReuseCapacityOnReturn(true); - assertEquals(true, gkoPool.getReuseCapacityOnReturn()); - } - - @Test - void testReuseCapacityOnMaintenanceConfig() throws Exception { - // Test default value - assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE, gkoPool.getReuseCapacityOnMaintenance()); - assertFalse(gkoPool.getReuseCapacityOnMaintenance()); - - // Test setting via config object - final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>(); - config.setReuseCapacityOnMaintenance(true); - assertEquals(true, config.getReuseCapacityOnMaintenance()); - - try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) { - assertEquals(true, pool.getReuseCapacityOnMaintenance()); - } - - // Test setter - gkoPool.setReuseCapacityOnMaintenance(true); - assertEquals(true, gkoPool.getReuseCapacityOnMaintenance()); - - gkoPool.setReuseCapacityOnMaintenance(false); - assertEquals(false, gkoPool.getReuseCapacityOnMaintenance()); - } - - /** - * Verify that when reuseCapacityOnMaintenance is true, eviction triggers reuseCapacity - * and when reuseCapacityOnReturn is false, returning an object does not trigger reuseCapacity. - * JIRA: POOL-350 - */ - @Test - @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS) - void testReuseCapacityOnMaintenanceBehavior() throws Exception { - gkoPool.setMaxTotalPerKey(2); - gkoPool.setMaxTotal(4); - gkoPool.setBlockWhenExhausted(true); - gkoPool.setMaxWait(Duration.ofSeconds(5)); - gkoPool.setReuseCapacityOnReturn(false); - gkoPool.setReuseCapacityOnMaintenance(true); - - // Create a waiter on key1 - final Thread waiter = new Thread(new SimpleTestThread<>(gkoPool, "key1")); - - // Exhaust capacity - final String obj1 = gkoPool.borrowObject("key2"); - final String obj2 = gkoPool.borrowObject("key2"); - final String obj3 = gkoPool.borrowObject("key3"); - final String obj4 = gkoPool.borrowObject("key3"); - - Thread.sleep(100); - - // Launch the waiter - it will be blocked - waiter.start(); - Thread.sleep(100); - - - // Return one object to free capacity - gkoPool.returnObject("key2", obj1); - - // Even with capacity available, waiter should still be blocked - // because reuseCapacityOnReturn is false - Thread.sleep(100); - assertTrue(waiter.isAlive()); - - // Call evict - with reuseCapacityOnMaintenance=true, this should serve the waiter - gkoPool.evict(); - Thread.sleep(100); - - // Waiter should have been served - assertFalse(waiter.isAlive()); - - // Clean up - gkoPool.returnObject("key2", obj2); - gkoPool.returnObject("key3", obj3); - gkoPool.returnObject("key3", obj4); - } } 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 538105e9..1633a047 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,18 @@ class TestGenericObjectPool extends TestBaseObjectPool { assertEquals(0, genericObjectPool.getNumActive(), "should be zero active"); } + @Test + void testAddObjectCanAddToMaxIdle() throws Exception { + genericObjectPool.setMaxTotal(5); + genericObjectPool.borrowObject(); + genericObjectPool.borrowObject(); + genericObjectPool.setMaxIdle(3); + for (int i = 0; i < 3; i++) { + genericObjectPool.addObject(); + } + assertEquals(3, genericObjectPool.getNumIdle()); + } + @Test @Timeout(value = 400, unit = TimeUnit.MILLISECONDS) void testAddObjectFastReturn() throws Exception { @@ -994,6 +1006,22 @@ class TestGenericObjectPool extends TestBaseObjectPool { } } + @Test + void testAddObjectRespectsMaxIdle() throws Exception { + genericObjectPool.setMaxIdle(1); + genericObjectPool.addObject(); + genericObjectPool.addObject(); // should be no-op + assertEquals(1, genericObjectPool.getNumIdle()); + } + + @Test + void testAddObjectRespectsMaxTotal() throws Exception { + genericObjectPool.setMaxTotal(1); + genericObjectPool.addObject(); + genericObjectPool.addObject(); // should be no-op + assertEquals(1, genericObjectPool.getNumIdle()); + } + @Test void testAppendStats() { assertFalse(genericObjectPool.getMessageStatistics()); @@ -2548,6 +2576,62 @@ class TestGenericObjectPool extends TestBaseObjectPool { assertEquals(1, genericObjectPool.getNumIdle()); } + /** + * Verify that when thread A borrows two objects from a pool with maxTotal=2, + * then two more threads try to borrow, returning one object and invalidating + * the other will serve both of the waiting threads. + */ + @Test + @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS) + void testReturnAndInvalidateServesWaiters() throws Exception { + genericObjectPool.setMaxTotal(2); + genericObjectPool.setBlockWhenExhausted(true); + genericObjectPool.setMaxWait(Duration.ofMillis(100)); + + // Thread A borrows two objects + final String obj1 = genericObjectPool.borrowObject(); + final String obj2 = genericObjectPool.borrowObject(); + + // Track successful borrows + final AtomicInteger successfulBorrows = new AtomicInteger(0); + + // Create two borrowing threads that will block + final Thread borrower1 = new Thread(() -> { + try { + genericObjectPool.borrowObject(); + successfulBorrows.incrementAndGet(); + } catch (final Exception e) { + // Borrow failed + } + }); + + final Thread borrower2 = new Thread(() -> { + try { + genericObjectPool.borrowObject(); + successfulBorrows.incrementAndGet(); + } catch (final Exception e) { + // Borrow failed + } + }); + + // Start the borrowing threads - they will block + borrower1.start(); + borrower2.start(); + Thread.sleep(50); // Give threads time to start and block + + // Thread A returns one object and invalidates the other with no delay + genericObjectPool.invalidateObject(obj1); + genericObjectPool.invalidateObject(obj2); + + // Wait for threads to complete + borrower1.join(); + borrower2.join(); + + // Both threads should have been served + assertEquals(2, successfulBorrows.get(), + "Both waiting threads should have been served, but only " + successfulBorrows.get() + " were served"); + } + @Test /* maxWaitMillis x2 + padding */ @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS) void testReturnBorrowObjectWithingMaxWaitDuration() throws Exception { @@ -2871,6 +2955,7 @@ class TestGenericObjectPool extends TestBaseObjectPool { } } + // POOL-276 @Test void testValidationOnCreateOnly() throws Exception { @@ -2968,7 +3053,6 @@ class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.close(); } - @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) void testWhenExhaustedFail() throws Exception { @@ -2981,88 +3065,4 @@ class TestGenericObjectPool extends TestBaseObjectPool { assertEquals(1, genericObjectPool.getNumIdle()); genericObjectPool.close(); } - - @Test - void testAddObjectRespectsMaxIdle() throws Exception { - genericObjectPool.setMaxIdle(1); - genericObjectPool.addObject(); - genericObjectPool.addObject(); // should be no-op - assertEquals(1, genericObjectPool.getNumIdle()); - } - - @Test - void testAddObjectRespectsMaxTotal() throws Exception { - genericObjectPool.setMaxTotal(1); - genericObjectPool.addObject(); - genericObjectPool.addObject(); // should be no-op - assertEquals(1, genericObjectPool.getNumIdle()); - } - - @Test - void testAddObjectCanAddToMaxIdle() throws Exception { - genericObjectPool.setMaxTotal(5); - genericObjectPool.borrowObject(); - genericObjectPool.borrowObject(); - genericObjectPool.setMaxIdle(3); - for (int i = 0; i < 3; i++) { - genericObjectPool.addObject(); - } - assertEquals(3, genericObjectPool.getNumIdle()); - } - - /** - * Verify that when thread A borrows two objects from a pool with maxTotal=2, - * then two more threads try to borrow, returning one object and invalidating - * the other will serve both of the waiting threads. - */ - @Test - @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS) - void testReturnAndInvalidateServesWaiters() throws Exception { - genericObjectPool.setMaxTotal(2); - genericObjectPool.setBlockWhenExhausted(true); - genericObjectPool.setMaxWait(Duration.ofMillis(100)); - - // Thread A borrows two objects - final String obj1 = genericObjectPool.borrowObject(); - final String obj2 = genericObjectPool.borrowObject(); - - // Track successful borrows - final AtomicInteger successfulBorrows = new AtomicInteger(0); - - // Create two borrowing threads that will block - final Thread borrower1 = new Thread(() -> { - try { - genericObjectPool.borrowObject(); - successfulBorrows.incrementAndGet(); - } catch (final Exception e) { - // Borrow failed - } - }); - - final Thread borrower2 = new Thread(() -> { - try { - genericObjectPool.borrowObject(); - successfulBorrows.incrementAndGet(); - } catch (final Exception e) { - // Borrow failed - } - }); - - // Start the borrowing threads - they will block - borrower1.start(); - borrower2.start(); - Thread.sleep(50); // Give threads time to start and block - - // Thread A returns one object and invalidates the other with no delay - genericObjectPool.invalidateObject(obj1); - genericObjectPool.invalidateObject(obj2); - - // Wait for threads to complete - borrower1.join(); - borrower2.join(); - - // Both threads should have been served - assertEquals(2, successfulBorrows.get(), - "Both waiting threads should have been served, but only " + successfulBorrows.get() + " were served"); - } }
