|
1 | 1 | /**
|
2 |
| - * Copyright 2009-2019 the original author or authors. |
| 2 | + * Copyright 2009-2020 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
15 | 15 | */
|
16 | 16 | package org.apache.ibatis.cache.decorators;
|
17 | 17 |
|
| 18 | +import java.text.MessageFormat; |
18 | 19 | import java.util.concurrent.ConcurrentHashMap;
|
| 20 | +import java.util.concurrent.CountDownLatch; |
19 | 21 | import java.util.concurrent.TimeUnit;
|
20 |
| -import java.util.concurrent.locks.Lock; |
21 |
| -import java.util.concurrent.locks.ReentrantLock; |
22 | 22 |
|
23 | 23 | import org.apache.ibatis.cache.Cache;
|
24 | 24 | import org.apache.ibatis.cache.CacheException;
|
25 | 25 |
|
26 | 26 | /**
|
27 |
| - * Simple blocking decorator |
| 27 | + * <p>Simple blocking decorator |
28 | 28 | *
|
29 |
| - * Simple and inefficient version of EhCache's BlockingCache decorator. |
| 29 | + * <p>Simple and inefficient version of EhCache's BlockingCache decorator. |
30 | 30 | * It sets a lock over a cache key when the element is not found in cache.
|
31 | 31 | * This way, other threads will wait until this element is filled instead of hitting the database.
|
32 | 32 | *
|
| 33 | + * <p>By its nature, this implementation can cause deadlock when used incorrecly. |
| 34 | + * |
33 | 35 | * @author Eduardo Macarron
|
34 | 36 | *
|
35 | 37 | */
|
36 | 38 | public class BlockingCache implements Cache {
|
37 | 39 |
|
38 | 40 | private long timeout;
|
39 | 41 | private final Cache delegate;
|
40 |
| - private final ConcurrentHashMap<Object, ReentrantLock> locks; |
| 42 | + private final ConcurrentHashMap<Object, CountDownLatch> locks; |
41 | 43 |
|
42 | 44 | public BlockingCache(Cache delegate) {
|
43 | 45 | this.delegate = delegate;
|
@@ -85,31 +87,35 @@ public void clear() {
|
85 | 87 | delegate.clear();
|
86 | 88 | }
|
87 | 89 |
|
88 |
| - private ReentrantLock getLockForKey(Object key) { |
89 |
| - return locks.computeIfAbsent(key, k -> new ReentrantLock()); |
90 |
| - } |
91 |
| - |
92 | 90 | 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 | + } |
95 | 97 | 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(); |
99 | 106 | }
|
100 | 107 | } catch (InterruptedException e) {
|
101 | 108 | throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
|
102 | 109 | }
|
103 |
| - } else { |
104 |
| - lock.lock(); |
105 | 110 | }
|
106 | 111 | }
|
107 | 112 |
|
108 | 113 | 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."); |
112 | 117 | }
|
| 118 | + latch.countDown(); |
113 | 119 | }
|
114 | 120 |
|
115 | 121 | public long getTimeout() {
|
|
0 commit comments