Skip to content

Commit 5fef385

Browse files
committed
Make reuseCapacity activation configurable. JIRA: POOL-350.
1 parent a76df55 commit 5fef385

File tree

4 files changed

+251
-1
lines changed

4 files changed

+251
-1
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ The <action> type attribute can be add,update,fix,remove.
4747
<body>
4848
<release version="2.13.0" date="YYYY-MM-DD" description="This is a feature and maintenance release. Java 8 or later is required.">
4949
<!-- FIX -->
50+
<action type="fix" issue="POOL-350" dev="psteitz" due-to="Phil Steitz">Make placement of calls to GKOP reuseCapacity configurable.</action>
5051
<action type="fix" issue="POOL-290" dev="psteitz" due-to="Serge Angelov">TestSoftRefOutOfMemory (unit test) can loop infinitely on failure.</action>
5152
<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>
5253
<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>

src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ public String toString() {
200200
private volatile int maxTotalPerKey =
201201
GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
202202

203+
private volatile boolean reuseCapacityOnReturn =
204+
GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_RETURN;
205+
206+
private volatile boolean reuseCapacityOnMaintenance =
207+
GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE;
208+
203209
private final KeyedPooledObjectFactory<K, T> factory;
204210

205211
private final boolean fairness;
@@ -1187,6 +1193,9 @@ public void evict() throws Exception {
11871193
if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
11881194
removeAbandoned(ac);
11891195
}
1196+
if (reuseCapacityOnMaintenance) {
1197+
reuseCapacity();
1198+
}
11901199
}
11911200

11921201
/**
@@ -1266,6 +1275,32 @@ public int getMinIdlePerKey() {
12661275
return Math.min(this.minIdlePerKey, maxIdlePerKeySave);
12671276
}
12681277

1278+
/**
1279+
* Gets whether to call {@link #reuseCapacity()} when returning objects to the pool.
1280+
* When true, the pool will check if there are threads waiting to borrow objects
1281+
* and attempt to reuse the capacity freed by the return operation.
1282+
*
1283+
* @return {@code true} if capacity reuse is enabled on return, {@code false} otherwise
1284+
* @see #setReuseCapacityOnReturn(boolean)
1285+
* @since 2.13.0
1286+
*/
1287+
public boolean getReuseCapacityOnReturn() {
1288+
return reuseCapacityOnReturn;
1289+
}
1290+
1291+
/**
1292+
* Gets whether to call {@link #reuseCapacity()} during pool maintenance (eviction).
1293+
* When true, the pool will attempt to reuse freed capacity at the end of each
1294+
* eviction run.
1295+
*
1296+
* @return {@code true} if capacity reuse is enabled during maintenance, {@code false} otherwise
1297+
* @see #setReuseCapacityOnMaintenance(boolean)
1298+
* @since 2.13.0
1299+
*/
1300+
public boolean getReuseCapacityOnMaintenance() {
1301+
return reuseCapacityOnMaintenance;
1302+
}
1303+
12691304
@Override
12701305
public int getNumActive() {
12711306
return numTotal.get() - getNumIdle();
@@ -1618,7 +1653,7 @@ public void returnObject(final K key, final T obj) {
16181653
}
16191654
}
16201655
} finally {
1621-
if (hasBorrowWaiters()) {
1656+
if (reuseCapacityOnReturn && hasBorrowWaiters()) {
16221657
reuseCapacity();
16231658
}
16241659
updateStatsReturn(activeTime);
@@ -1697,6 +1732,8 @@ public void setConfig(final GenericKeyedObjectPoolConfig<T> conf) {
16971732
setMaxTotalPerKey(conf.getMaxTotalPerKey());
16981733
setMaxTotal(conf.getMaxTotal());
16991734
setMinIdlePerKey(conf.getMinIdlePerKey());
1735+
setReuseCapacityOnReturn(conf.getReuseCapacityOnReturn());
1736+
setReuseCapacityOnMaintenance(conf.getReuseCapacityOnMaintenance());
17001737
}
17011738

17021739
/**
@@ -1753,6 +1790,34 @@ public void setMinIdlePerKey(final int minIdlePerKey) {
17531790
this.minIdlePerKey = minIdlePerKey;
17541791
}
17551792

1793+
/**
1794+
* Sets whether to call {@link #reuseCapacity()} when returning objects to the pool.
1795+
* When enabled, the pool will check if there are threads waiting to borrow objects
1796+
* and attempt to reuse capacity (across pools) on return.
1797+
*
1798+
* @param reuseCapacityOnReturn {@code true} to enable capacity reuse on return,
1799+
* {@code false} to disable
1800+
* @see #getReuseCapacityOnReturn()
1801+
* @since 2.13.0
1802+
*/
1803+
public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) {
1804+
this.reuseCapacityOnReturn = reuseCapacityOnReturn;
1805+
}
1806+
1807+
/**
1808+
* Sets whether to call {@link #reuseCapacity()} during pool maintenance (eviction).
1809+
* When enabled, the pool will attempt to reuse capacity at the end of each
1810+
* eviction run.
1811+
*
1812+
* @param reuseCapacityOnMaintenance {@code true} to enable capacity reuse during
1813+
* maintenance, {@code false} to disable
1814+
* @see #getReuseCapacityOnMaintenance()
1815+
* @since 2.13.0
1816+
*/
1817+
public void setReuseCapacityOnMaintenance(final boolean reuseCapacityOnMaintenance) {
1818+
this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance;
1819+
}
1820+
17561821
@Override
17571822
protected void toStringAppendFields(final StringBuilder builder) {
17581823
super.toStringAppendFields(builder);
@@ -1780,6 +1845,10 @@ protected void toStringAppendFields(final StringBuilder builder) {
17801845
builder.append(evictionKey);
17811846
builder.append(", abandonedConfig=");
17821847
builder.append(abandonedConfig);
1848+
builder.append(", reuseCapacityOnReturn=");
1849+
builder.append(reuseCapacityOnReturn);
1850+
builder.append(", reuseCapacityOnMaintenance=");
1851+
builder.append(reuseCapacityOnMaintenance);
17831852
}
17841853

17851854
/**

src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPoolConfig.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ public class GenericKeyedObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {
5858
*/
5959
public static final int DEFAULT_MAX_IDLE_PER_KEY = 8;
6060

61+
/**
62+
* The default value for the {@code reuseCapacityOnReturn} configuration attribute: {@value}.
63+
*
64+
* @see GenericKeyedObjectPool#getReuseCapacityOnReturn()
65+
* @since 2.13.0
66+
*/
67+
public static final boolean DEFAULT_REUSE_CAPACITY_ON_RETURN = true;
68+
69+
/**
70+
* The default value for the {@code reuseCapacityOnMaintenance} configuration attribute: {@value}.
71+
*
72+
* @see GenericKeyedObjectPool#getReuseCapacityOnMaintenance()
73+
* @since 2.13.0
74+
*/
75+
public static final boolean DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE = false;
76+
6177
private int minIdlePerKey = DEFAULT_MIN_IDLE_PER_KEY;
6278

6379
private int maxIdlePerKey = DEFAULT_MAX_IDLE_PER_KEY;
@@ -66,6 +82,10 @@ public class GenericKeyedObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {
6682

6783
private int maxTotal = DEFAULT_MAX_TOTAL;
6884

85+
private boolean reuseCapacityOnReturn = DEFAULT_REUSE_CAPACITY_ON_RETURN;
86+
87+
private boolean reuseCapacityOnMaintenance = DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE;
88+
6989
/**
7090
* Constructs a new configuration with default settings.
7191
*/
@@ -134,6 +154,34 @@ public int getMinIdlePerKey() {
134154
return minIdlePerKey;
135155
}
136156

157+
/**
158+
* Gets the value for the {@code reuseCapacityOnReturn} configuration attribute
159+
* for pools created with this configuration instance.
160+
*
161+
* @return The current setting of {@code reuseCapacityOnReturn} for this
162+
* configuration instance
163+
*
164+
* @see GenericKeyedObjectPool#getReuseCapacityOnReturn()
165+
* @since 2.13.0
166+
*/
167+
public boolean getReuseCapacityOnReturn() {
168+
return reuseCapacityOnReturn;
169+
}
170+
171+
/**
172+
* Gets the value for the {@code reuseCapacityOnMaintenance} configuration attribute
173+
* for pools created with this configuration instance.
174+
*
175+
* @return The current setting of {@code reuseCapacityOnMaintenance} for this
176+
* configuration instance
177+
*
178+
* @see GenericKeyedObjectPool#getReuseCapacityOnMaintenance()
179+
* @since 2.13.0
180+
*/
181+
public boolean getReuseCapacityOnMaintenance() {
182+
return reuseCapacityOnMaintenance;
183+
}
184+
137185
/**
138186
* Sets the value for the {@code maxIdlePerKey} configuration attribute for
139187
* pools created with this configuration instance.
@@ -186,6 +234,34 @@ public void setMinIdlePerKey(final int minIdlePerKey) {
186234
this.minIdlePerKey = minIdlePerKey;
187235
}
188236

237+
/**
238+
* Sets the value for the {@code reuseCapacityOnReturn} configuration attribute for
239+
* pools created with this configuration instance.
240+
*
241+
* @param reuseCapacityOnReturn The new setting of {@code reuseCapacityOnReturn}
242+
* for this configuration instance
243+
*
244+
* @see GenericKeyedObjectPool#setReuseCapacityOnReturn(boolean)
245+
* @since 2.13.0
246+
*/
247+
public void setReuseCapacityOnReturn(final boolean reuseCapacityOnReturn) {
248+
this.reuseCapacityOnReturn = reuseCapacityOnReturn;
249+
}
250+
251+
/**
252+
* Sets the value for the {@code reuseCapacityOnMaintenance} configuration attribute for
253+
* pools created with this configuration instance.
254+
*
255+
* @param reuseCapacityOnMaintenance The new setting of {@code reuseCapacityOnMaintenance}
256+
* for this configuration instance
257+
*
258+
* @see GenericKeyedObjectPool#setReuseCapacityOnMaintenance(boolean)
259+
* @since 2.13.0
260+
*/
261+
public void setReuseCapacityOnMaintenance(final boolean reuseCapacityOnMaintenance) {
262+
this.reuseCapacityOnMaintenance = reuseCapacityOnMaintenance;
263+
}
264+
189265
@Override
190266
protected void toStringAppendFields(final StringBuilder builder) {
191267
super.toStringAppendFields(builder);
@@ -197,5 +273,9 @@ protected void toStringAppendFields(final StringBuilder builder) {
197273
builder.append(maxTotalPerKey);
198274
builder.append(", maxTotal=");
199275
builder.append(maxTotal);
276+
builder.append(", reuseCapacityOnReturn=");
277+
builder.append(reuseCapacityOnReturn);
278+
builder.append(", reuseCapacityOnMaintenance=");
279+
builder.append(reuseCapacityOnMaintenance);
200280
}
201281
}

src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,4 +2660,104 @@ void testWhenExhaustedBlockClosePool() throws Exception {
26602660
// Check thread was interrupted
26612661
assertTrue(wtt.thrown instanceof InterruptedException);
26622662
}
2663+
2664+
2665+
@Test
2666+
void testReuseCapacityOnReturnConfig() throws Exception {
2667+
// Test default value
2668+
assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_RETURN, gkoPool.getReuseCapacityOnReturn());
2669+
assertTrue(gkoPool.getReuseCapacityOnReturn());
2670+
2671+
// Test setting via config object
2672+
final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
2673+
config.setReuseCapacityOnReturn(false);
2674+
assertEquals(false, config.getReuseCapacityOnReturn());
2675+
2676+
try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) {
2677+
assertEquals(false, pool.getReuseCapacityOnReturn());
2678+
}
2679+
2680+
// Test setter
2681+
gkoPool.setReuseCapacityOnReturn(false);
2682+
assertEquals(false, gkoPool.getReuseCapacityOnReturn());
2683+
2684+
gkoPool.setReuseCapacityOnReturn(true);
2685+
assertEquals(true, gkoPool.getReuseCapacityOnReturn());
2686+
}
2687+
2688+
@Test
2689+
void testReuseCapacityOnMaintenanceConfig() throws Exception {
2690+
// Test default value
2691+
assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE, gkoPool.getReuseCapacityOnMaintenance());
2692+
assertFalse(gkoPool.getReuseCapacityOnMaintenance());
2693+
2694+
// Test setting via config object
2695+
final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
2696+
config.setReuseCapacityOnMaintenance(true);
2697+
assertEquals(true, config.getReuseCapacityOnMaintenance());
2698+
2699+
try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) {
2700+
assertEquals(true, pool.getReuseCapacityOnMaintenance());
2701+
}
2702+
2703+
// Test setter
2704+
gkoPool.setReuseCapacityOnMaintenance(true);
2705+
assertEquals(true, gkoPool.getReuseCapacityOnMaintenance());
2706+
2707+
gkoPool.setReuseCapacityOnMaintenance(false);
2708+
assertEquals(false, gkoPool.getReuseCapacityOnMaintenance());
2709+
}
2710+
2711+
/**
2712+
* Verify that when reuseCapacityOnMaintenance is true, eviction triggers reuseCapacity
2713+
* and when reuseCapacityOnReturn is false, returning an object does not trigger reuseCapacity.
2714+
* JIRA: POOL-350
2715+
*/
2716+
@Test
2717+
@Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS)
2718+
void testReuseCapacityOnMaintenanceBehavior() throws Exception {
2719+
gkoPool.setMaxTotalPerKey(2);
2720+
gkoPool.setMaxTotal(4);
2721+
gkoPool.setBlockWhenExhausted(true);
2722+
gkoPool.setMaxWait(Duration.ofSeconds(5));
2723+
gkoPool.setReuseCapacityOnReturn(false);
2724+
gkoPool.setReuseCapacityOnMaintenance(true);
2725+
2726+
// Create a waiter on key1
2727+
final Thread waiter = new Thread(new SimpleTestThread<>(gkoPool, "key1"));
2728+
2729+
// Exhaust capacity
2730+
final String obj1 = gkoPool.borrowObject("key2");
2731+
final String obj2 = gkoPool.borrowObject("key2");
2732+
final String obj3 = gkoPool.borrowObject("key3");
2733+
final String obj4 = gkoPool.borrowObject("key3");
2734+
2735+
Thread.sleep(100);
2736+
2737+
// Launch the waiter - it will be blocked
2738+
waiter.start();
2739+
Thread.sleep(100);
2740+
2741+
2742+
// Return one object to free capacity
2743+
gkoPool.returnObject("key2", obj1);
2744+
2745+
// Even with capacity available, waiter should still be blocked
2746+
// because reuseCapacityOnReturn is false
2747+
Thread.sleep(100);
2748+
assertTrue(waiter.isAlive());
2749+
2750+
// Call evict - with reuseCapacityOnMaintenance=true, this should serve the waiter
2751+
gkoPool.evict();
2752+
Thread.sleep(100);
2753+
2754+
// Waiter should have been served
2755+
assertFalse(waiter.isAlive());
2756+
2757+
// Clean up
2758+
gkoPool.returnObject("key2", obj2);
2759+
gkoPool.returnObject("key3", obj3);
2760+
gkoPool.returnObject("key3", obj4);
2761+
}
26632762
}
2763+

0 commit comments

Comments
 (0)