1
1
package io .lettuce .core .support .caching ;
2
2
3
- import java .util .List ;
4
- import java .util .concurrent .Callable ;
5
- import java .util .concurrent .CopyOnWriteArrayList ;
6
- import java .util .function .Consumer ;
7
-
8
3
import io .lettuce .core .StatefulRedisConnectionImpl ;
9
4
import io .lettuce .core .TrackingArgs ;
10
5
import io .lettuce .core .api .StatefulRedisConnection ;
11
6
import io .lettuce .core .codec .RedisCodec ;
7
+ import java .util .List ;
8
+ import java .util .concurrent .Callable ;
9
+ import java .util .concurrent .ConcurrentHashMap ;
10
+ import java .util .concurrent .CopyOnWriteArrayList ;
11
+ import java .util .concurrent .locks .ReentrantLock ;
12
+ import java .util .function .Consumer ;
12
13
13
14
/**
14
15
* Utility to provide server-side assistance for client-side caches. This is a {@link CacheFrontend} that represents a two-level
@@ -41,6 +42,8 @@ public class ClientSideCaching<K, V> implements CacheFrontend<K, V> {
41
42
42
43
private final List <Consumer <K >> invalidationListeners = new CopyOnWriteArrayList <>();
43
44
45
+ private final ConcurrentHashMap <K , ReentrantLock > keyLocks = new ConcurrentHashMap <>();
46
+
44
47
private ClientSideCaching (CacheAccessor <K , V > cacheAccessor , RedisCache <K , V > redisCache ) {
45
48
this .cacheAccessor = cacheAccessor ;
46
49
this .redisCache = redisCache ;
@@ -49,12 +52,12 @@ private ClientSideCaching(CacheAccessor<K, V> cacheAccessor, RedisCache<K, V> re
49
52
/**
50
53
* Enable server-assisted Client side caching for the given {@link CacheAccessor} and {@link StatefulRedisConnection}.
51
54
* <p>
52
- * Note that the {@link CacheFrontend} is associated with a Redis connection. Make sure to {@link CacheFrontend#close()
53
- * close} the frontend object to release the Redis connection after use.
55
+ * Note that the {@link CacheFrontend} is associated with a Redis connection. Make sure to
56
+ * {@link CacheFrontend#close() close} the frontend object to release the Redis connection after use.
54
57
*
55
58
* @param cacheAccessor the accessor used to interact with the client-side cache.
56
59
* @param connection the Redis connection to use. The connection will be associated with {@link CacheFrontend} and must be
57
- * closed through {@link CacheFrontend#close()}.
60
+ * closed through {@link CacheFrontend#close()}.
58
61
* @param tracking the tracking parameters.
59
62
* @param <K> Key type.
60
63
* @param <V> Value type.
@@ -72,12 +75,12 @@ public static <K, V> CacheFrontend<K, V> enable(CacheAccessor<K, V> cacheAccesso
72
75
* Create a server-assisted Client side caching for the given {@link CacheAccessor} and {@link StatefulRedisConnection}.
73
76
* This method expects that client key tracking is already configured.
74
77
* <p>
75
- * Note that the {@link CacheFrontend} is associated with a Redis connection. Make sure to {@link CacheFrontend#close()
76
- * close} the frontend object to release the Redis connection after use.
78
+ * Note that the {@link CacheFrontend} is associated with a Redis connection. Make sure to
79
+ * {@link CacheFrontend#close() close} the frontend object to release the Redis connection after use.
77
80
*
78
81
* @param cacheAccessor the accessor used to interact with the client-side cache.
79
82
* @param connection the Redis connection to use. The connection will be associated with {@link CacheFrontend} and must be
80
- * closed through {@link CacheFrontend#close()}.
83
+ * closed through {@link CacheFrontend#close()}.
81
84
* @param <K> Key type.
82
85
* @param <V> Value type.
83
86
* @return the {@link CacheFrontend} for value retrieval.
@@ -103,6 +106,7 @@ private static <K, V> CacheFrontend<K, V> create(CacheAccessor<K, V> cacheAccess
103
106
}
104
107
105
108
private void notifyInvalidate (K key ) {
109
+ keyLocks .remove (key );
106
110
107
111
for (java .util .function .Consumer <K > invalidationListener : invalidationListeners ) {
108
112
invalidationListener .accept (key );
@@ -111,6 +115,7 @@ private void notifyInvalidate(K key) {
111
115
112
116
@ Override
113
117
public void close () {
118
+ keyLocks .clear ();
114
119
redisCache .close ();
115
120
}
116
121
@@ -124,10 +129,20 @@ public V get(K key) {
124
129
V value = cacheAccessor .get (key );
125
130
126
131
if (value == null ) {
127
- value = redisCache .get (key );
132
+ ReentrantLock keyLock = keyLocks .computeIfAbsent (key , k -> new ReentrantLock ());
133
+ keyLock .lock ();
134
+ try {
135
+ value = cacheAccessor .get (key );
136
+
137
+ if (value == null ) {
138
+ value = redisCache .get (key );
128
139
129
- if (value != null ) {
130
- cacheAccessor .put (key , value );
140
+ if (value != null ) {
141
+ cacheAccessor .put (key , value );
142
+ }
143
+ }
144
+ } finally {
145
+ keyLock .unlock ();
131
146
}
132
147
}
133
148
@@ -140,28 +155,38 @@ public V get(K key, Callable<V> valueLoader) {
140
155
V value = cacheAccessor .get (key );
141
156
142
157
if (value == null ) {
143
- value = redisCache .get (key );
144
-
145
- if (value == null ) {
158
+ ReentrantLock keyLock = keyLocks .computeIfAbsent (key , k -> new ReentrantLock ());
159
+ keyLock .lock ();
146
160
147
- try {
148
- value = valueLoader .call ();
149
- } catch (Exception e ) {
150
- throw new ValueRetrievalException (
151
- String .format ("Value loader %s failed with an exception for key %s" , valueLoader , key ), e );
152
- }
161
+ try {
162
+ value = cacheAccessor .get (key );
153
163
154
164
if (value == null ) {
155
- throw new ValueRetrievalException (
156
- String .format ("Value loader %s returned a null value for key %s" , valueLoader , key ));
157
- }
158
- redisCache .put (key , value );
165
+ value = redisCache .get (key );
159
166
160
- // register interest in key
161
- redisCache .get (key );
162
- }
167
+ if (value == null ) {
168
+ try {
169
+ value = valueLoader .call ();
170
+ } catch (Exception e ) {
171
+ throw new ValueRetrievalException (
172
+ String .format ("Value loader %s failed with an exception for key %s" , valueLoader , key ), e );
173
+ }
163
174
164
- cacheAccessor .put (key , value );
175
+ if (value == null ) {
176
+ throw new ValueRetrievalException (
177
+ String .format ("Value loader %s returned a null value for key %s" , valueLoader , key ));
178
+ }
179
+
180
+ redisCache .put (key , value );
181
+
182
+ redisCache .get (key );
183
+ }
184
+
185
+ cacheAccessor .put (key , value );
186
+ }
187
+ } finally {
188
+ keyLock .unlock ();
189
+ }
165
190
}
166
191
167
192
return value ;
0 commit comments