This is an automated email from the ASF dual-hosted git repository.

psteitz pushed a commit to branch POOL_2_X
in repository https://gitbox.apache.org/repos/asf/commons-pool.git


The following commit(s) were added to refs/heads/POOL_2_X by this push:
     new 5fef385a Make reuseCapacity activation configurable. JIRA: POOL-350.
5fef385a is described below

commit 5fef385ac68fa953e168e29c51cb28921ce4c316
Author: Phil Steitz <[email protected]>
AuthorDate: Mon Nov 3 18:52:10 2025 -0700

    Make reuseCapacity activation configurable. JIRA: POOL-350.
---
 src/changes/changes.xml                            |   1 +
 .../commons/pool2/impl/GenericKeyedObjectPool.java |  71 ++++++++++++++-
 .../pool2/impl/GenericKeyedObjectPoolConfig.java   |  80 +++++++++++++++++
 .../pool2/impl/TestGenericKeyedObjectPool.java     | 100 +++++++++++++++++++++
 4 files changed, 251 insertions(+), 1 deletion(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 37b4bd61..b6865f13 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -47,6 +47,7 @@ The <action> type attribute can be add,update,fix,remove.
   <body>
   <release version="2.13.0" date="YYYY-MM-DD" description="This is a feature 
and maintenance release. Java 8 or later is required.">
     <!-- FIX -->
+    <action type="fix" issue="POOL-350" dev="psteitz" due-to="Phil 
Steitz">Make placement of calls to GKOP reuseCapacity configurable.</action>
     <action type="fix" issue="POOL-290" dev="psteitz" due-to="Serge 
Angelov">TestSoftRefOutOfMemory (unit test) can loop infinitely on 
failure.</action>
     <action type="fix" issue="POOL-419" dev="psteitz" due-to="Raju Gupta, Phil 
Steitz">GenericObjectPool counters and object collections can be corrupted when 
returnObject and invalidate are invoked concurrently by client threads on the 
same pooled object.</action>
     <action type="fix" issue="POOL-421" dev="psteitz" due-to="Phil 
Steitz">GenericObjectPool addObject should return immediately when there is no 
capacity to add.</action>
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 d1d10597..fa2923dd 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
@@ -200,6 +200,12 @@ public class GenericKeyedObjectPool<K, T> extends 
BaseGenericObjectPool<T>
     private volatile int maxTotalPerKey =
             GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
 
+    private volatile boolean reuseCapacityOnReturn =
+            GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_RETURN;
+
+    private volatile boolean reuseCapacityOnMaintenance =
+            GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE;
+
     private final KeyedPooledObjectFactory<K, T> factory;
 
     private final boolean fairness;
@@ -1187,6 +1193,9 @@ public class GenericKeyedObjectPool<K, T> extends 
BaseGenericObjectPool<T>
         if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
             removeAbandoned(ac);
         }
+        if (reuseCapacityOnMaintenance) {
+            reuseCapacity();
+        }
     }
 
     /**
@@ -1266,6 +1275,32 @@ 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();
@@ -1618,7 +1653,7 @@ public class GenericKeyedObjectPool<K, T> extends 
BaseGenericObjectPool<T>
                 }
             }
         } finally {
-            if (hasBorrowWaiters()) {
+            if (reuseCapacityOnReturn && hasBorrowWaiters()) {
                 reuseCapacity();
             }
             updateStatsReturn(activeTime);
@@ -1697,6 +1732,8 @@ public class GenericKeyedObjectPool<K, T> extends 
BaseGenericObjectPool<T>
         setMaxTotalPerKey(conf.getMaxTotalPerKey());
         setMaxTotal(conf.getMaxTotal());
         setMinIdlePerKey(conf.getMinIdlePerKey());
+        setReuseCapacityOnReturn(conf.getReuseCapacityOnReturn());
+        setReuseCapacityOnMaintenance(conf.getReuseCapacityOnMaintenance());
     }
 
     /**
@@ -1753,6 +1790,34 @@ 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
+     * eviction run.
+     *
+     * @param reuseCapacityOnMaintenance {@code true} to enable capacity reuse 
during
+     *                                   maintenance, {@code false} to disable
+     * @see #getReuseCapacityOnMaintenance()
+     * @since 2.13.0
+     */
+    public void setReuseCapacityOnMaintenance(final boolean 
reuseCapacityOnMaintenance) {
+        this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance;
+    }
+
     @Override
     protected void toStringAppendFields(final StringBuilder builder) {
         super.toStringAppendFields(builder);
@@ -1780,6 +1845,10 @@ public class GenericKeyedObjectPool<K, T> extends 
BaseGenericObjectPool<T>
         builder.append(evictionKey);
         builder.append(", abandonedConfig=");
         builder.append(abandonedConfig);
+        builder.append(", reuseCapacityOnReturn=");
+        builder.append(reuseCapacityOnReturn);
+        builder.append(", reuseCapacityOnMaintenance=");
+        builder.append(reuseCapacityOnMaintenance);
     }
 
     /**
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 cf95b41f..0523104e 100644
--- 
a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java
+++ 
b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java
@@ -58,6 +58,22 @@ public class GenericKeyedObjectPoolConfig<T> extends 
BaseObjectPoolConfig<T> {
      */
     public static final int DEFAULT_MAX_IDLE_PER_KEY = 8;
 
+    /**
+     * The default value for the {@code reuseCapacityOnReturn} configuration 
attribute: {@value}.
+     *
+     * @see GenericKeyedObjectPool#getReuseCapacityOnReturn()
+     * @since 2.13.0
+     */
+    public static final boolean DEFAULT_REUSE_CAPACITY_ON_RETURN = true;
+
+    /**
+     * The default value for the {@code reuseCapacityOnMaintenance} 
configuration attribute: {@value}.
+     *
+     * @see GenericKeyedObjectPool#getReuseCapacityOnMaintenance()
+     * @since 2.13.0
+     */
+    public static final boolean DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE = false;
+
     private int minIdlePerKey = DEFAULT_MIN_IDLE_PER_KEY;
 
     private int maxIdlePerKey = DEFAULT_MAX_IDLE_PER_KEY;
@@ -66,6 +82,10 @@ public class GenericKeyedObjectPoolConfig<T> extends 
BaseObjectPoolConfig<T> {
 
     private int maxTotal = DEFAULT_MAX_TOTAL;
 
+    private boolean reuseCapacityOnReturn = DEFAULT_REUSE_CAPACITY_ON_RETURN;
+
+    private boolean reuseCapacityOnMaintenance = 
DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE;
+
     /**
      * Constructs a new configuration with default settings.
      */
@@ -134,6 +154,34 @@ public class GenericKeyedObjectPoolConfig<T> extends 
BaseObjectPoolConfig<T> {
         return minIdlePerKey;
     }
 
+    /**
+     * Gets the value for the {@code reuseCapacityOnReturn} configuration 
attribute
+     * for pools created with this configuration instance.
+     *
+     * @return  The current setting of {@code reuseCapacityOnReturn} for this
+     *          configuration instance
+     *
+     * @see GenericKeyedObjectPool#getReuseCapacityOnReturn()
+     * @since 2.13.0
+     */
+    public boolean getReuseCapacityOnReturn() {
+        return reuseCapacityOnReturn;
+    }
+
+    /**
+     * Gets the value for the {@code reuseCapacityOnMaintenance} configuration 
attribute
+     * for pools created with this configuration instance.
+     *
+     * @return  The current setting of {@code reuseCapacityOnMaintenance} for 
this
+     *          configuration instance
+     *
+     * @see GenericKeyedObjectPool#getReuseCapacityOnMaintenance()
+     * @since 2.13.0
+     */
+    public boolean getReuseCapacityOnMaintenance() {
+        return reuseCapacityOnMaintenance;
+    }
+
     /**
      * Sets the value for the {@code maxIdlePerKey} configuration attribute for
      * pools created with this configuration instance.
@@ -186,6 +234,34 @@ public class GenericKeyedObjectPoolConfig<T> extends 
BaseObjectPoolConfig<T> {
         this.minIdlePerKey = minIdlePerKey;
     }
 
+    /**
+     * Sets the value for the {@code reuseCapacityOnReturn} configuration 
attribute for
+     * pools created with this configuration instance.
+     *
+     * @param reuseCapacityOnReturn The new setting of {@code 
reuseCapacityOnReturn}
+     *        for this configuration instance
+     *
+     * @see GenericKeyedObjectPool#setReuseCapacityOnReturn(boolean)
+     * @since 2.13.0
+     */
+    public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) {
+        this.reuseCapacityOnReturn = reuseCapacityOnReturn;
+    }
+
+    /**
+     * Sets the value for the {@code reuseCapacityOnMaintenance} configuration 
attribute for
+     * pools created with this configuration instance.
+     *
+     * @param reuseCapacityOnMaintenance The new setting of {@code 
reuseCapacityOnMaintenance}
+     *        for this configuration instance
+     *
+     * @see GenericKeyedObjectPool#setReuseCapacityOnMaintenance(boolean)
+     * @since 2.13.0
+     */
+    public void setReuseCapacityOnMaintenance(final boolean 
reuseCapacityOnMaintenance) {
+        this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance;
+    }
+
     @Override
     protected void toStringAppendFields(final StringBuilder builder) {
         super.toStringAppendFields(builder);
@@ -197,5 +273,9 @@ public class GenericKeyedObjectPoolConfig<T> extends 
BaseObjectPoolConfig<T> {
         builder.append(maxTotalPerKey);
         builder.append(", maxTotal=");
         builder.append(maxTotal);
+        builder.append(", reuseCapacityOnReturn=");
+        builder.append(reuseCapacityOnReturn);
+        builder.append(", reuseCapacityOnMaintenance=");
+        builder.append(reuseCapacityOnMaintenance);
     }
 }
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 0d7f4199..02224c02 100644
--- 
a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
+++ 
b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java
@@ -2660,4 +2660,104 @@ 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);
+    }
 }
+

Reply via email to