@@ -106,7 +106,39 @@ K getKey() {
106106 return localKey ;
107107 }
108108
109- V getValue (K key , boolean requireSameKeyInstance ) {
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+ */
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 ) {
110142 long stamp = lock .tryOptimisticRead ();
111143 K localKey = this .key ;
112144 V localValue = this .value ;
@@ -116,6 +148,11 @@ V getValue(K key, boolean requireSameKeyInstance) {
116148 localValue = this .value ;
117149 lock .unlockRead (stamp );
118150 }
151+
152+ // check that the given key matches the key associated with the value in the entry
153+ // this is used to detect if the entry has already been recycled and contains another key
154+ // when requireSameKeyInstance is true, the key must be exactly the same instance as the one stored in the
155+ // entry to match
119156 if (localKey != key && (requireSameKeyInstance || localKey == null || !localKey .equals (key ))) {
120157 return null ;
121158 }
@@ -236,34 +273,45 @@ public boolean exists(Key key) {
236273 * The caller is responsible for releasing the reference.
237274 */
238275 public Value get (Key key ) {
239- return getValue (key , entries .get (key ), false );
276+ return getValueFromWrapper (key , entries .get (key ));
240277 }
241278
242- private Value getValue (Key key , EntryWrapper <Key , Value > valueWrapper , boolean requireSameKeyInstance ) {
279+ private Value getValueFromWrapper (Key key , EntryWrapper <Key , Value > valueWrapper ) {
243280 if (valueWrapper == null ) {
244281 return null ;
245282 } else {
246- Value value = valueWrapper .getValue (key , requireSameKeyInstance );
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+ Value value = valueWrapper .getValue (key );
284+ return getRetainedValueMatchingKey (key , value );
285+ }
286+ }
287+
288+ private Value getValueMatchingEntry (Map .Entry <Key , EntryWrapper <Key , Value >> entry ) {
289+ Value valueMatchingEntry = EntryWrapper .getValueMatchingMapEntry (entry );
290+ return getRetainedValueMatchingKey (entry .getKey (), valueMatchingEntry );
291+ }
292+
293+ // validates that the value matches the key and that the value has not been recycled
294+ // which are possible due to the lack of exclusive locks in the cache and the use of reference counted objects
295+ private Value getRetainedValueMatchingKey (Key key , Value value ) {
296+ if (value == null ) {
297+ // the wrapper has been recycled and contains another key
298+ return null ;
299+ }
300+ try {
301+ value .retain ();
302+ } catch (IllegalReferenceCountException e ) {
303+ // Value was already deallocated
304+ return null ;
305+ }
306+ // check that the value matches the key and that there's at least 2 references to it since
307+ // the cache should be holding one reference and a new reference was just added in this method
308+ if (value .refCnt () > 1 && value .matchesKey (key )) {
309+ return value ;
310+ } else {
311+ // Value or IdentityWrapper was recycled and already contains another value
312+ // release the reference added in this method
313+ value .release ();
314+ return null ;
267315 }
268316 }
269317
@@ -280,7 +328,7 @@ public Collection<Value> getRange(Key first, Key last) {
280328
281329 // Return the values of the entries found in cache
282330 for (Map .Entry <Key , EntryWrapper <Key , Value >> entry : entries .subMap (first , true , last , true ).entrySet ()) {
283- Value value = getValue (entry . getKey (), entry . getValue (), true );
331+ Value value = getValueMatchingEntry (entry );
284332 if (value != null ) {
285333 values .add (value );
286334 }
@@ -297,6 +345,9 @@ public Collection<Value> getRange(Key first, Key last) {
297345 * @return an pair of ints, containing the number of removed entries and the total size
298346 */
299347 public Pair <Integer , Long > removeRange (Key first , Key last , boolean lastInclusive ) {
348+ if (log .isDebugEnabled ()) {
349+ log .debug ("Removing entries in range [{}, {}], lastInclusive: {}" , first , last , lastInclusive );
350+ }
300351 RemovalCounters counters = RemovalCounters .create ();
301352 Map <Key , EntryWrapper <Key , Value >> subMap = entries .subMap (first , true , last , lastInclusive );
302353 for (Map .Entry <Key , EntryWrapper <Key , Value >> entry : subMap .entrySet ()) {
@@ -320,7 +371,7 @@ private RemoveEntryResult removeEntry(Map.Entry<Key, EntryWrapper<Key, Value>> e
320371 boolean skipInvalid , Predicate <Value > removeCondition ) {
321372 Key key = entry .getKey ();
322373 EntryWrapper <Key , Value > entryWrapper = entry .getValue ();
323- Value value = entryWrapper . getValue ( key , true );
374+ Value value = getValueMatchingEntry ( entry );
324375 if (value == null ) {
325376 // the wrapper has already been recycled and contains another key
326377 if (!skipInvalid ) {
@@ -404,6 +455,9 @@ private Pair<Integer, Long> handleRemovalResult(RemovalCounters counters) {
404455 * @return a pair containing the number of entries evicted and their total size
405456 */
406457 public Pair <Integer , Long > evictLeastAccessedEntries (long minSize ) {
458+ if (log .isDebugEnabled ()) {
459+ log .debug ("Evicting entries to reach a minimum size of {}" , minSize );
460+ }
407461 checkArgument (minSize > 0 );
408462 RemovalCounters counters = RemovalCounters .create ();
409463 while (counters .removedSize < minSize && !Thread .currentThread ().isInterrupted ()) {
@@ -422,6 +476,9 @@ public Pair<Integer, Long> evictLeastAccessedEntries(long minSize) {
422476 * @return the tota
423477 */
424478 public Pair <Integer , Long > evictLEntriesBeforeTimestamp (long maxTimestamp ) {
479+ if (log .isDebugEnabled ()) {
480+ log .debug ("Evicting entries with timestamp <= {}" , maxTimestamp );
481+ }
425482 RemovalCounters counters = RemovalCounters .create ();
426483 while (!Thread .currentThread ().isInterrupted ()) {
427484 Map .Entry <Key , EntryWrapper <Key , Value >> entry = entries .firstEntry ();
@@ -453,6 +510,9 @@ public long getSize() {
453510 * @return size of removed entries
454511 */
455512 public Pair <Integer , Long > clear () {
513+ if (log .isDebugEnabled ()) {
514+ log .debug ("Clearing the cache with {} entries and size {}" , entries .size (), size .get ());
515+ }
456516 RemovalCounters counters = RemovalCounters .create ();
457517 while (!Thread .currentThread ().isInterrupted ()) {
458518 Map .Entry <Key , EntryWrapper <Key , Value >> entry = entries .firstEntry ();
0 commit comments