@@ -106,7 +106,39 @@ K getKey() {
106
106
return localKey ;
107
107
}
108
108
109
+ /**
110
+ * Get the value associated with the key. Returns null if the key does not match the key.
111
+ *
112
+ * @param key the key to match
113
+ * @return the value associated with the key, or null if the value has already been recycled or the key does not
114
+ * match
115
+ */
109
116
V getValue (K key ) {
117
+ return getValueInternal (key , false );
118
+ }
119
+
120
+ /**
121
+ * Get the value associated with the Map.Entry's key and value. Exact instance of the key is required to match.
122
+ * @param entry the entry which contains the key and {@link EntryWrapper} value to get the value from
123
+ * @return the value associated with the key, or null if the value has already been recycled or the key does not
124
+ * exactly match the same instance
125
+ */
126
+ static <K , V > V getValueMatchingMapEntry (Map .Entry <K , EntryWrapper <K , V >> entry ) {
127
+ return entry .getValue ().getValueInternal (entry .getKey (), true );
128
+ }
129
+
130
+ /**
131
+ * Get the value associated with the key. Returns null if the key does not match the key associated with the
132
+ * value.
133
+ *
134
+ * @param key the key to match
135
+ * @param requireSameKeyInstance when true, the matching will be restricted to exactly the same instance of the
136
+ * key as the one stored in the wrapper. This is used to avoid any races
137
+ * when retrieving or removing the entries from the cache when the key and value
138
+ * instances are available.
139
+ * @return the value associated with the key, or null if the key does not match
140
+ */
141
+ private V getValueInternal (K key , boolean requireSameKeyInstance ) {
110
142
long stamp = lock .tryOptimisticRead ();
111
143
K localKey = this .key ;
112
144
V localValue = this .value ;
@@ -116,7 +148,11 @@ V getValue(K key) {
116
148
localValue = this .value ;
117
149
lock .unlockRead (stamp );
118
150
}
119
- if (localKey != key ) {
151
+ // check that the given key matches the key associated with the value in the entry
152
+ // this is used to detect if the entry has already been recycled and contains another key
153
+ // when requireSameKeyInstance is true, the key must be exactly the same instance as the one stored in the
154
+ // entry to match
155
+ if (localKey != key && (requireSameKeyInstance || localKey == null || !localKey .equals (key ))) {
120
156
return null ;
121
157
}
122
158
return localValue ;
@@ -236,34 +272,45 @@ public boolean exists(Key key) {
236
272
* The caller is responsible for releasing the reference.
237
273
*/
238
274
public Value get (Key key ) {
239
- return getValue (key , entries .get (key ));
275
+ return getValueFromWrapper (key , entries .get (key ));
240
276
}
241
277
242
- private Value getValue (Key key , EntryWrapper <Key , Value > valueWrapper ) {
278
+ private Value getValueFromWrapper (Key key , EntryWrapper <Key , Value > valueWrapper ) {
243
279
if (valueWrapper == null ) {
244
280
return null ;
245
281
} else {
246
282
Value value = valueWrapper .getValue (key );
247
- if (value == null ) {
248
- // the wrapper has been recycled and contains another key
249
- return null ;
250
- }
251
- try {
252
- value .retain ();
253
- } catch (IllegalReferenceCountException e ) {
254
- // Value was already deallocated
255
- return null ;
256
- }
257
- // check that the value matches the key and that there's at least 2 references to it since
258
- // the cache should be holding one reference and a new reference was just added in this method
259
- if (value .refCnt () > 1 && value .matchesKey (key )) {
260
- return value ;
261
- } else {
262
- // Value or IdentityWrapper was recycled and already contains another value
263
- // release the reference added in this method
264
- value .release ();
265
- return null ;
266
- }
283
+ return getRetainedValueMatchingKey (key , value );
284
+ }
285
+ }
286
+
287
+ private Value getValueMatchingEntry (Map .Entry <Key , EntryWrapper <Key , Value >> entry ) {
288
+ Value valueMatchingEntry = EntryWrapper .getValueMatchingMapEntry (entry );
289
+ return getRetainedValueMatchingKey (entry .getKey (), valueMatchingEntry );
290
+ }
291
+
292
+ // validates that the value matches the key and that the value has not been recycled
293
+ // which are possible due to the lack of exclusive locks in the cache and the use of reference counted objects
294
+ private Value getRetainedValueMatchingKey (Key key , Value value ) {
295
+ if (value == null ) {
296
+ // the wrapper has been recycled and contains another key
297
+ return null ;
298
+ }
299
+ try {
300
+ value .retain ();
301
+ } catch (IllegalReferenceCountException e ) {
302
+ // Value was already deallocated
303
+ return null ;
304
+ }
305
+ // check that the value matches the key and that there's at least 2 references to it since
306
+ // the cache should be holding one reference and a new reference was just added in this method
307
+ if (value .refCnt () > 1 && value .matchesKey (key )) {
308
+ return value ;
309
+ } else {
310
+ // Value or IdentityWrapper was recycled and already contains another value
311
+ // release the reference added in this method
312
+ value .release ();
313
+ return null ;
267
314
}
268
315
}
269
316
@@ -280,7 +327,7 @@ public Collection<Value> getRange(Key first, Key last) {
280
327
281
328
// Return the values of the entries found in cache
282
329
for (Map .Entry <Key , EntryWrapper <Key , Value >> entry : entries .subMap (first , true , last , true ).entrySet ()) {
283
- Value value = getValue (entry . getKey (), entry . getValue () );
330
+ Value value = getValueMatchingEntry (entry );
284
331
if (value != null ) {
285
332
values .add (value );
286
333
}
@@ -297,6 +344,9 @@ public Collection<Value> getRange(Key first, Key last) {
297
344
* @return an pair of ints, containing the number of removed entries and the total size
298
345
*/
299
346
public Pair <Integer , Long > removeRange (Key first , Key last , boolean lastInclusive ) {
347
+ if (log .isDebugEnabled ()) {
348
+ log .debug ("Removing entries in range [{}, {}], lastInclusive: {}" , first , last , lastInclusive );
349
+ }
300
350
RemovalCounters counters = RemovalCounters .create ();
301
351
Map <Key , EntryWrapper <Key , Value >> subMap = entries .subMap (first , true , last , lastInclusive );
302
352
for (Map .Entry <Key , EntryWrapper <Key , Value >> entry : subMap .entrySet ()) {
@@ -320,7 +370,7 @@ private RemoveEntryResult removeEntry(Map.Entry<Key, EntryWrapper<Key, Value>> e
320
370
boolean skipInvalid , Predicate <Value > removeCondition ) {
321
371
Key key = entry .getKey ();
322
372
EntryWrapper <Key , Value > entryWrapper = entry .getValue ();
323
- Value value = entryWrapper . getValue ( key );
373
+ Value value = getValueMatchingEntry ( entry );
324
374
if (value == null ) {
325
375
// the wrapper has already been recycled and contains another key
326
376
if (!skipInvalid ) {
@@ -404,6 +454,9 @@ private Pair<Integer, Long> handleRemovalResult(RemovalCounters counters) {
404
454
* @return a pair containing the number of entries evicted and their total size
405
455
*/
406
456
public Pair <Integer , Long > evictLeastAccessedEntries (long minSize ) {
457
+ if (log .isDebugEnabled ()) {
458
+ log .debug ("Evicting entries to reach a minimum size of {}" , minSize );
459
+ }
407
460
checkArgument (minSize > 0 );
408
461
RemovalCounters counters = RemovalCounters .create ();
409
462
while (counters .removedSize < minSize && !Thread .currentThread ().isInterrupted ()) {
@@ -422,6 +475,9 @@ public Pair<Integer, Long> evictLeastAccessedEntries(long minSize) {
422
475
* @return the tota
423
476
*/
424
477
public Pair <Integer , Long > evictLEntriesBeforeTimestamp (long maxTimestamp ) {
478
+ if (log .isDebugEnabled ()) {
479
+ log .debug ("Evicting entries with timestamp <= {}" , maxTimestamp );
480
+ }
425
481
RemovalCounters counters = RemovalCounters .create ();
426
482
while (!Thread .currentThread ().isInterrupted ()) {
427
483
Map .Entry <Key , EntryWrapper <Key , Value >> entry = entries .firstEntry ();
@@ -453,6 +509,9 @@ public long getSize() {
453
509
* @return size of removed entries
454
510
*/
455
511
public Pair <Integer , Long > clear () {
512
+ if (log .isDebugEnabled ()) {
513
+ log .debug ("Clearing the cache with {} entries and size {}" , entries .size (), size .get ());
514
+ }
456
515
RemovalCounters counters = RemovalCounters .create ();
457
516
while (!Thread .currentThread ().isInterrupted ()) {
458
517
Map .Entry <Key , EntryWrapper <Key , Value >> entry = entries .firstEntry ();
0 commit comments