Skip to content

Commit 1bf82a6

Browse files
committed
Fix potential ConcurrentModificationException in Reaper clean-up thread.
1 parent 3199aa4 commit 1bf82a6

File tree

3 files changed

+34
-6
lines changed

3 files changed

+34
-6
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ The <action> type attribute can be add,update,fix,remove.
5555
<action type="fix" dev="ggregory" due-to="Gary Gregory">Remove -nouses directive from maven-bundle-plugin. OSGi package imports now state 'uses' definitions for package imports, this doesn't affect JPMS (from org.apache.commons:commons-parent:80).</action>
5656
<action type="fix" dev="ggregory" due-to="Gary Gregory">Operation on the "idleHighWaterMark" shared variable in "ErodingFactor" class is not atomic [org.apache.commons.pool3.PoolUtils$ErodingFactor] At PoolUtils.java:[line 101] AT_NONATOMIC_OPERATIONS_ON_SHARED_VARIABLE.</action>
5757
<action type="fix" dev="ggregory" due-to="shengulong, Gary Gregory" issue="POOL-422">org.apache.commons.pool3.impl.GenericObjectPool.create(Duration) duration computation doesn't match 2.x. See also PR #409 from shengulong.</action>
58+
<action type="fix" dev="markt" due-to="Coverity Scan">Fix potential ConcurrentModificationException in EvictionTimer thread clean-up.</action>
5859
<!-- REMOVE -->
5960
<action dev="ggregory" type="remove">Deprecations from version 2.x have been removed.</action>
6061
<!-- UPDATE -->

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

Lines changed: 15 additions & 0 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,6 +74,20 @@ private static final class Reaper implements Runnable {
7374
@Override
7475
public void run() {
7576
synchronized (EvictionTimer.class) {
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();
85+
if (entry.getKey().get() == null) {
86+
executor.remove(entry.getValue());
87+
iterator.remove();
88+
}
89+
}
90+
7691
for (final Entry<WeakReference<BaseGenericObjectPool<?, ?>.Evictor>, WeakRunner<BaseGenericObjectPool<?, ?>.Evictor>> entry : TASK_MAP
7792
.entrySet()) {
7893
if (entry.getKey().get() == null) {

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -973,13 +973,25 @@ public void tearDown() throws Exception {
973973
void testAbandonedPool() throws TestException, InterruptedException {
974974
final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
975975
config.setJmxEnabled(false);
976-
GenericObjectPool<String, TestException> abandoned = new GenericObjectPool<>(simpleFactory, config);
977-
abandoned.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
978976

979-
// This is ugly, but forces GC to hit the pool
980-
final WeakReference<GenericObjectPool<String, TestException>> ref = new WeakReference<>(abandoned);
981-
abandoned = null;
982-
while (ref.get() != null) {
977+
// Need to create at least 2 pools to test EvictorTimer Reaper for ConcurrentModificationException
978+
// Note: If the test fails, the ConcurrentModificationException isn't visible without modifications to the
979+
// Reaper class.
980+
GenericObjectPool<String, TestException> abandoned1 = new GenericObjectPool<>(simpleFactory, config);
981+
abandoned1.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
982+
GenericObjectPool<String, TestException> abandoned2 = new GenericObjectPool<>(simpleFactory, config);
983+
abandoned2.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor
984+
985+
// This is ugly, but forces GC to hit the pools
986+
final WeakReference<GenericObjectPool<String, TestException>> ref1 = new WeakReference<>(abandoned1);
987+
abandoned1 = null;
988+
while (ref1.get() != null) {
989+
System.gc();
990+
Thread.sleep(100);
991+
}
992+
final WeakReference<GenericObjectPool<String, TestException>> ref2 = new WeakReference<>(abandoned2);
993+
abandoned2 = null;
994+
while (ref2.get() != null) {
983995
System.gc();
984996
Thread.sleep(100);
985997
}

0 commit comments

Comments
 (0)