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