@@ -106,7 +106,39 @@ K getKey() {
106106 return localKey ;
107107 }
108108
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+ */
109116 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 ) {
110142 long stamp = lock .tryOptimisticRead ();
111143 K localKey = this .key ;
112144 V localValue = this .value ;
@@ -116,7 +148,11 @@ V getValue(K key) {
116148 localValue = this .value ;
117149 lock .unlockRead (stamp );
118150 }
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 ))) {
120156 return null ;
121157 }
122158 return localValue ;
@@ -236,34 +272,45 @@ public boolean exists(Key key) {
236272 * The caller is responsible for releasing the reference.
237273 */
238274 public Value get (Key key ) {
239- return getValue (key , entries .get (key ));
275+ return getValueFromWrapper (key , entries .get (key ));
240276 }
241277
242- private Value getValue (Key key , EntryWrapper <Key , Value > valueWrapper ) {
278+ private Value getValueFromWrapper (Key key , EntryWrapper <Key , Value > valueWrapper ) {
243279 if (valueWrapper == null ) {
244280 return null ;
245281 } else {
246282 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 ;
267314 }
268315 }
269316
@@ -280,7 +327,7 @@ public Collection<Value> getRange(Key first, Key last) {
280327
281328 // Return the values of the entries found in cache
282329 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 );
284331 if (value != null ) {
285332 values .add (value );
286333 }
@@ -297,6 +344,9 @@ public Collection<Value> getRange(Key first, Key last) {
297344 * @return an pair of ints, containing the number of removed entries and the total size
298345 */
299346 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+ }
300350 RemovalCounters counters = RemovalCounters .create ();
301351 Map <Key , EntryWrapper <Key , Value >> subMap = entries .subMap (first , true , last , lastInclusive );
302352 for (Map .Entry <Key , EntryWrapper <Key , Value >> entry : subMap .entrySet ()) {
@@ -320,7 +370,7 @@ private RemoveEntryResult removeEntry(Map.Entry<Key, EntryWrapper<Key, Value>> e
320370 boolean skipInvalid , Predicate <Value > removeCondition ) {
321371 Key key = entry .getKey ();
322372 EntryWrapper <Key , Value > entryWrapper = entry .getValue ();
323- Value value = entryWrapper . getValue ( key );
373+ Value value = getValueMatchingEntry ( entry );
324374 if (value == null ) {
325375 // the wrapper has already been recycled and contains another key
326376 if (!skipInvalid ) {
@@ -404,6 +454,9 @@ private Pair<Integer, Long> handleRemovalResult(RemovalCounters counters) {
404454 * @return a pair containing the number of entries evicted and their total size
405455 */
406456 public Pair <Integer , Long > evictLeastAccessedEntries (long minSize ) {
457+ if (log .isDebugEnabled ()) {
458+ log .debug ("Evicting entries to reach a minimum size of {}" , minSize );
459+ }
407460 checkArgument (minSize > 0 );
408461 RemovalCounters counters = RemovalCounters .create ();
409462 while (counters .removedSize < minSize && !Thread .currentThread ().isInterrupted ()) {
@@ -422,6 +475,9 @@ public Pair<Integer, Long> evictLeastAccessedEntries(long minSize) {
422475 * @return the tota
423476 */
424477 public Pair <Integer , Long > evictLEntriesBeforeTimestamp (long maxTimestamp ) {
478+ if (log .isDebugEnabled ()) {
479+ log .debug ("Evicting entries with timestamp <= {}" , maxTimestamp );
480+ }
425481 RemovalCounters counters = RemovalCounters .create ();
426482 while (!Thread .currentThread ().isInterrupted ()) {
427483 Map .Entry <Key , EntryWrapper <Key , Value >> entry = entries .firstEntry ();
@@ -453,6 +509,9 @@ public long getSize() {
453509 * @return size of removed entries
454510 */
455511 public Pair <Integer , Long > clear () {
512+ if (log .isDebugEnabled ()) {
513+ log .debug ("Clearing the cache with {} entries and size {}" , entries .size (), size .get ());
514+ }
456515 RemovalCounters counters = RemovalCounters .create ();
457516 while (!Thread .currentThread ().isInterrupted ()) {
458517 Map .Entry <Key , EntryWrapper <Key , Value >> entry = entries .firstEntry ();
0 commit comments