Home | History | Annotate | Download | only in cache
      1 /*
      2  * Copyright (C) 2011 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package com.google.common.cache;
     16 
     17 import static com.google.common.cache.LocalCache.Strength.STRONG;
     18 import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
     19 import static com.google.common.collect.Maps.immutableEntry;
     20 import static org.junit.contrib.truth.Truth.ASSERT;
     21 
     22 import com.google.common.base.Function;
     23 import com.google.common.cache.LocalCache.Strength;
     24 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
     25 import com.google.common.collect.ImmutableSet;
     26 import com.google.common.collect.Iterables;
     27 
     28 import junit.framework.TestCase;
     29 
     30 import java.lang.ref.WeakReference;
     31 
     32 /**
     33  * Tests of basic {@link LoadingCache} operations with all possible combinations of key & value
     34  * strengths.
     35  *
     36  * @author mike nonemacher
     37  */
     38 public class CacheReferencesTest extends TestCase {
     39 
     40   private static final CacheLoader<Key,String> KEY_TO_STRING_LOADER =
     41       new CacheLoader<Key, String>() {
     42         @Override public String load(Key key) {
     43           return key.toString();
     44         }
     45       };
     46 
     47   private CacheBuilderFactory factoryWithAllKeyStrengths() {
     48     return new CacheBuilderFactory()
     49         .withKeyStrengths(ImmutableSet.of(STRONG, Strength.WEAK))
     50         .withValueStrengths(ImmutableSet.of(STRONG, Strength.WEAK, Strength.SOFT));
     51   }
     52 
     53   private Iterable<LoadingCache<Key, String>> caches() {
     54     CacheBuilderFactory factory = factoryWithAllKeyStrengths();
     55     return Iterables.transform(factory.buildAllPermutations(),
     56         new Function<CacheBuilder<Object, Object>, LoadingCache<Key, String>>() {
     57           @Override public LoadingCache<Key, String> apply(CacheBuilder<Object, Object> builder) {
     58             return builder.build(KEY_TO_STRING_LOADER);
     59           }
     60         });
     61   }
     62 
     63   public void testContainsKeyAndValue() {
     64     for (LoadingCache<Key, String> cache : caches()) {
     65       // maintain strong refs so these won't be collected, regardless of cache's key/value strength
     66       Key key = new Key(1);
     67       String value = key.toString();
     68       assertSame(value, cache.getUnchecked(key));
     69       assertTrue(cache.asMap().containsKey(key));
     70       assertTrue(cache.asMap().containsValue(value));
     71       assertEquals(1, cache.size());
     72     }
     73   }
     74 
     75   public void testClear() {
     76     for (LoadingCache<Key, String> cache : caches()) {
     77       Key key = new Key(1);
     78       String value = key.toString();
     79       assertSame(value, cache.getUnchecked(key));
     80       assertFalse(cache.asMap().isEmpty());
     81       cache.invalidateAll();
     82       assertEquals(0, cache.size());
     83       assertTrue(cache.asMap().isEmpty());
     84       assertFalse(cache.asMap().containsKey(key));
     85       assertFalse(cache.asMap().containsValue(value));
     86     }
     87   }
     88 
     89   public void testKeySetEntrySetValues() {
     90     for (LoadingCache<Key, String> cache : caches()) {
     91       Key key1 = new Key(1);
     92       String value1 = key1.toString();
     93       Key key2 = new Key(2);
     94       String value2 = key2.toString();
     95       assertSame(value1, cache.getUnchecked(key1));
     96       assertSame(value2, cache.getUnchecked(key2));
     97       assertEquals(ImmutableSet.of(key1, key2), cache.asMap().keySet());
     98       ASSERT.that(cache.asMap().values()).hasContentsAnyOrder(value1, value2);
     99       assertEquals(ImmutableSet.of(immutableEntry(key1, value1), immutableEntry(key2, value2)),
    100           cache.asMap().entrySet());
    101     }
    102   }
    103 
    104   public void testInvalidate() {
    105     for (LoadingCache<Key, String> cache : caches()) {
    106       Key key1 = new Key(1);
    107       String value1 = key1.toString();
    108       Key key2 = new Key(2);
    109       String value2 = key2.toString();
    110       assertSame(value1, cache.getUnchecked(key1));
    111       assertSame(value2, cache.getUnchecked(key2));
    112       cache.invalidate(key1);
    113       assertFalse(cache.asMap().containsKey(key1));
    114       assertTrue(cache.asMap().containsKey(key2));
    115       assertEquals(1, cache.size());
    116       assertEquals(ImmutableSet.of(key2), cache.asMap().keySet());
    117       ASSERT.that(cache.asMap().values()).hasContentsAnyOrder(value2);
    118       assertEquals(ImmutableSet.of(immutableEntry(key2, value2)), cache.asMap().entrySet());
    119     }
    120   }
    121 
    122   public void testCleanupOnReferenceCollection() {
    123     for (CacheBuilder<Object, Object> builder
    124         : factoryWithAllKeyStrengths().buildAllPermutations()) {
    125       if (builder.keyStrength == STRONG && builder.valueStrength == STRONG) {
    126         continue;
    127       }
    128       CountingRemovalListener<Integer, String> removalListener = countingRemovalListener();
    129       CacheLoader<Integer, String> toStringLoader =
    130           new CacheLoader<Integer, String>() {
    131             @Override public String load(Integer key) {
    132               return key.toString();
    133             }
    134           };
    135       LoadingCache<Integer, String> cache =
    136           builder.removalListener(removalListener).build(toStringLoader);
    137 
    138       // ints in [-128, 127] have their wrappers precomputed and cached, so they won't be GCed
    139       Integer key1 = 1001;
    140       Integer key2 = 1002;
    141       String value1 = cache.getUnchecked(key1);
    142       String value2 = cache.getUnchecked(key2);
    143       // make (key1, value1) eligible for collection
    144       key1 = null;
    145       value1 = null;
    146       assertCleanup(cache, removalListener);
    147       // make sure the GC isn't going to see key2 or value2 as dead in assertCleanup
    148       assertSame(value2, cache.getUnchecked(key2));
    149     }
    150   }
    151 
    152   private void assertCleanup(LoadingCache<Integer, String> cache,
    153       CountingRemovalListener<Integer, String> removalListener) {
    154 
    155     // initialSize will most likely be 2, but it's possible for the GC to have already run, so we'll
    156     // observe a size of 1
    157     long initialSize = cache.size();
    158     assertTrue(initialSize == 1 || initialSize == 2);
    159 
    160     // wait up to 5s
    161     byte[] filler = new byte[1024];
    162     for (int i = 0; i < 500; i++) {
    163       System.gc();
    164 
    165       CacheTesting.drainReferenceQueues(cache);
    166       if (cache.size() == 1) {
    167         break;
    168       }
    169       try {
    170         Thread.sleep(10);
    171       } catch (InterruptedException e) { /* ignore */}
    172       try {
    173         // Fill up heap so soft references get cleared.
    174         filler = new byte[Math.max(filler.length, filler.length * 2)];
    175       } catch (OutOfMemoryError e) {}
    176     }
    177 
    178     CacheTesting.processPendingNotifications(cache);
    179     assertEquals(1, cache.size());
    180     assertEquals(1, removalListener.getCount());
    181   }
    182 
    183   // A simple type whose .toString() will return the same value each time, but without maintaining
    184   // a strong reference to that value.
    185   static class Key {
    186     private final int value;
    187     private WeakReference<String> toString;
    188 
    189     Key(int value) {
    190       this.value = value;
    191     }
    192 
    193     @Override public synchronized String toString() {
    194       String s;
    195       if (toString != null) {
    196         s = toString.get();
    197         if (s != null) {
    198           return s;
    199         }
    200       }
    201       s = Integer.toString(value);
    202       toString = new WeakReference<String>(s);
    203       return s;
    204     }
    205   }
    206 }
    207