Skip to content

Commit 0d90852

Browse files
committed
Fix potential ConcurrentModificationException in Reaper clean-up thread.
1 parent 19e7e2c commit 0d90852

File tree

3 files changed

+30
-10
lines changed

3 files changed

+30
-10
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ The <action> type attribute can be add,update,fix,remove.
6161
<action type="fix" dev="ggregory" due-to="Wei Guo, Gary Gregory">Fix site link from the About page to the Download page, see also #387.</action>
6262
<action type="fix" dev="ggregory" due-to="Gary Gregory">Operation on the "idleHighWaterMark" shared variable in "ErodingFactor" class is not atomic [org.apache.commons.pool2.PoolUtils$ErodingFactor] At PoolUtils.java:[line 98] AT_NONATOMIC_OPERATIONS_ON_SHARED_VARIABLE.</action>
6363
<action type="fix" dev="ggregory" due-to="Gary Gregory">org.apache.commons.pool2.impl.GenericObjectPool.create(Duration) should normalize a negative duration to zero.</action>
64+
<action type="fix" dev="markt" due-to="Coverity Scan">Fix potential ConcurrentModificationException in EvictionTimer thread clean-up.</action>
6465
<!-- ADD -->
6566
<action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.pool2.PooledObject.nonNull(PooledObject).</action>
6667
<action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.pool2.PooledObject.getObject(PooledObject).</action>

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.security.PrivilegedAction;
2222
import java.time.Duration;
2323
import java.util.HashMap;
24+
import java.util.Iterator;
2425
import java.util.Map.Entry;
2526
import java.util.concurrent.ScheduledFuture;
2627
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -73,11 +74,17 @@ private static final class Reaper implements Runnable {
7374
@Override
7475
public void run() {
7576
synchronized (EvictionTimer.class) {
76-
for (final Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> entry : TASK_MAP
77-
.entrySet()) {
77+
/*
78+
* Need to use iterator over TASK_MAP so entries can be removed when iterating without triggering a
79+
* ConcurrentModificationException.
80+
*/
81+
final Iterator<Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>>> iterator =
82+
TASK_MAP.entrySet().iterator();
83+
while (iterator.hasNext()) {
84+
final Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> entry = iterator.next();
7885
if (entry.getKey().get() == null) {
7986
executor.remove(entry.getValue());
80-
TASK_MAP.remove(entry.getKey());
87+
iterator.remove();
8188
}
8289
}
8390
if (TASK_MAP.isEmpty() && executor != null) {

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -936,13 +936,25 @@ public void tearDown() throws Exception {
936936
void testAbandonedPool() throws Exception {
937937
final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
938938
config.setJmxEnabled(false);
939-
GenericObjectPool<String> abandoned = new GenericObjectPool<>(simpleFactory, config);
940-
abandoned.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
941-
assertEquals(abandoned.getRemoveAbandonedTimeout(), abandoned.getRemoveAbandonedTimeoutDuration().getSeconds());
942-
// This is ugly, but forces GC to hit the pool
943-
final WeakReference<GenericObjectPool<String>> ref = new WeakReference<>(abandoned);
944-
abandoned = null;
945-
while (ref.get() != null) {
939+
940+
// Need to create at least 2 pools to test EvictorTimer Reaper for ConcurrentModificationException
941+
// Note: If the test fails, the ConcurrentModificationException isn't visible without modifications to the
942+
// Reaper class.
943+
GenericObjectPool<String> abandoned1 = new GenericObjectPool<>(simpleFactory, config);
944+
abandoned1.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
945+
GenericObjectPool<String> abandoned2 = new GenericObjectPool<>(simpleFactory, config);
946+
abandoned2.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
947+
948+
// This is ugly, but forces GC to hit the pools
949+
final WeakReference<GenericObjectPool<String>> ref1 = new WeakReference<>(abandoned1);
950+
abandoned1 = null;
951+
while (ref1.get() != null) {
952+
System.gc();
953+
Thread.sleep(100);
954+
}
955+
final WeakReference<GenericObjectPool<String>> ref2 = new WeakReference<>(abandoned2);
956+
abandoned2 = null;
957+
while (ref2.get() != null) {
946958
System.gc();
947959
Thread.sleep(100);
948960
}

0 commit comments

Comments
 (0)