修复AbstractCacheputWithoutLock方法可能导致的外部资源泄露问题(pr#3958@Github)

This commit is contained in:
Looly 2025-06-20 17:34:37 +08:00
parent 698443860f
commit 68bcc8f5de
4 changed files with 37 additions and 2 deletions

View File

@ -109,7 +109,9 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
final MutableObj<K> mKey = MutableObj.of(key); final MutableObj<K> mKey = MutableObj.of(key);
// issue#3618 对于替换的键值对不做满队列检查和清除 // issue#3618 对于替换的键值对不做满队列检查和清除
if (cacheMap.containsKey(mKey)) { final CacheObj<K, V> oldObj = cacheMap.get(mKey);
if (null != oldObj) {
onRemove(oldObj.key, oldObj.obj);
// 存在相同key覆盖之 // 存在相同key覆盖之
cacheMap.put(mKey, co); cacheMap.put(mKey, co);
} else { } else {

View File

@ -17,8 +17,12 @@
package cn.hutool.v7.core.cache.impl; package cn.hutool.v7.core.cache.impl;
import cn.hutool.v7.core.collection.iter.CopiedIter; import cn.hutool.v7.core.collection.iter.CopiedIter;
import cn.hutool.v7.core.collection.set.SetUtil;
import cn.hutool.v7.core.lang.mutable.Mutable;
import java.io.Serial;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -31,6 +35,7 @@ import java.util.concurrent.locks.ReentrantLock;
* @author Looly * @author Looly
*/ */
public abstract class LockedCache<K, V> extends AbstractCache<K, V> { public abstract class LockedCache<K, V> extends AbstractCache<K, V> {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
@ -98,7 +103,15 @@ public abstract class LockedCache<K, V> extends AbstractCache<K, V> {
public void clear() { public void clear() {
lock.lock(); lock.lock();
try { try {
cacheMap.clear(); // 获取所有键的副本
final Set<Mutable<K>> keys = SetUtil.of(cacheMap.keySet());
CacheObj<K, V> co;
for (final Mutable<K> key : keys) {
co = removeWithoutLock(key.get());
if (null != co) {
onRemove(co.key, co.obj); // 触发资源释放
}
}
} finally { } finally {
lock.unlock(); lock.unlock();
} }

View File

@ -19,6 +19,7 @@ package cn.hutool.v7.core.cache.impl;
import cn.hutool.v7.core.cache.GlobalPruneTimer; import cn.hutool.v7.core.cache.GlobalPruneTimer;
import cn.hutool.v7.core.lang.mutable.Mutable; import cn.hutool.v7.core.lang.mutable.Mutable;
import java.io.Serial;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@ -34,6 +35,7 @@ import java.util.concurrent.ScheduledFuture;
* @param <V> 值类型 * @param <V> 值类型
*/ */
public class TimedReentrantCache<K, V> extends LockedCache<K, V> { public class TimedReentrantCache<K, V> extends LockedCache<K, V> {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 正在执行的定时任务 */ /** 正在执行的定时任务 */

View File

@ -164,4 +164,22 @@ public class CacheTest {
assertFalse(ALARM_CACHE.containsKey(1)); assertFalse(ALARM_CACHE.containsKey(1));
assertEquals(1, counter.get()); assertEquals(1, counter.get());
} }
/**
* ReentrantCache类clear()方法AbstractCache.putWithoutLock方法可能导致资源泄露
* https://github.com/chinabugotech/hutool/issues/3957
*/
@Test
public void reentrantCache_clear_Method_Test() {
AtomicInteger removeCount = new AtomicInteger();
Cache<String, String> lruCache = CacheUtil.newLRUCache(4);
lruCache.setListener((key, cachedObject) -> removeCount.getAndIncrement());
lruCache.put("key1","String1");
lruCache.put("key2","String2");
lruCache.put("key3","String3");
lruCache.put("key1","String4");//key已经存在原始putWithoutLock方法存在资源泄露
lruCache.put("key4","String5");
lruCache.clear();//ReentrantCache类clear()方法存在资源泄露
Assertions.assertEquals(5, removeCount.get());
}
} }