Skip to content

Commit b94c110

Browse files
authored
Merge pull request #2044 from harawata/blockingcache-countdownlatch
Fix possible OutOfMemoryError in BlockingCache
2 parents d069dd8 + fc3401b commit b94c110

File tree

1 file changed

+26
-20
lines changed

1 file changed

+26
-20
lines changed

src/main/java/org/apache/ibatis/cache/decorators/BlockingCache.java

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2019 the original author or authors.
2+
* Copyright 2009-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,29 +15,31 @@
1515
*/
1616
package org.apache.ibatis.cache.decorators;
1717

18+
import java.text.MessageFormat;
1819
import java.util.concurrent.ConcurrentHashMap;
20+
import java.util.concurrent.CountDownLatch;
1921
import java.util.concurrent.TimeUnit;
20-
import java.util.concurrent.locks.Lock;
21-
import java.util.concurrent.locks.ReentrantLock;
2222

2323
import org.apache.ibatis.cache.Cache;
2424
import org.apache.ibatis.cache.CacheException;
2525

2626
/**
27-
* Simple blocking decorator
27+
* <p>Simple blocking decorator
2828
*
29-
* Simple and inefficient version of EhCache's BlockingCache decorator.
29+
* <p>Simple and inefficient version of EhCache's BlockingCache decorator.
3030
* It sets a lock over a cache key when the element is not found in cache.
3131
* This way, other threads will wait until this element is filled instead of hitting the database.
3232
*
33+
* <p>By its nature, this implementation can cause deadlock when used incorrecly.
34+
*
3335
* @author Eduardo Macarron
3436
*
3537
*/
3638
public class BlockingCache implements Cache {
3739

3840
private long timeout;
3941
private final Cache delegate;
40-
private final ConcurrentHashMap<Object, ReentrantLock> locks;
42+
private final ConcurrentHashMap<Object, CountDownLatch> locks;
4143

4244
public BlockingCache(Cache delegate) {
4345
this.delegate = delegate;
@@ -85,31 +87,35 @@ public void clear() {
8587
delegate.clear();
8688
}
8789

88-
private ReentrantLock getLockForKey(Object key) {
89-
return locks.computeIfAbsent(key, k -> new ReentrantLock());
90-
}
91-
9290
private void acquireLock(Object key) {
93-
Lock lock = getLockForKey(key);
94-
if (timeout > 0) {
91+
CountDownLatch newLatch = new CountDownLatch(1);
92+
while (true) {
93+
CountDownLatch latch = locks.putIfAbsent(key, newLatch);
94+
if (latch == null) {
95+
break;
96+
}
9597
try {
96-
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
97-
if (!acquired) {
98-
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
98+
if (timeout > 0) {
99+
boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
100+
if (!acquired) {
101+
throw new CacheException(
102+
"Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
103+
}
104+
} else {
105+
latch.await();
99106
}
100107
} catch (InterruptedException e) {
101108
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
102109
}
103-
} else {
104-
lock.lock();
105110
}
106111
}
107112

108113
private void releaseLock(Object key) {
109-
ReentrantLock lock = locks.get(key);
110-
if (lock.isHeldByCurrentThread()) {
111-
lock.unlock();
114+
CountDownLatch latch = locks.remove(key);
115+
if (latch == null) {
116+
throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
112117
}
118+
latch.countDown();
113119
}
114120

115121
public long getTimeout() {

0 commit comments

Comments
 (0)