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);
+ }
}
+