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.TestingCacheLoaders.identityLoader;
18 import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
19 import static com.google.common.cache.TestingWeighers.constantWeigher;
20 import static com.google.common.cache.TestingWeighers.intKeyWeigher;
21 import static com.google.common.cache.TestingWeighers.intValueWeigher;
22 import static com.google.common.truth.Truth.assertThat;
23 import static java.util.Arrays.asList;
24 
25 import com.google.common.cache.CacheTesting.Receiver;
26 import com.google.common.cache.TestingCacheLoaders.IdentityLoader;
27 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
28 import java.util.List;
29 import java.util.Set;
30 import junit.framework.TestCase;
31 
32 /**
33  * Tests relating to cache eviction: what does and doesn't count toward maximumSize, what happens
34  * when maximumSize is reached, etc.
35  *
36  * @author mike nonemacher
37  */
38 public class CacheEvictionTest extends TestCase {
39   static final int MAX_SIZE = 100;
40 
testEviction_setMaxSegmentSize()41   public void testEviction_setMaxSegmentSize() {
42     IdentityLoader<Object> loader = identityLoader();
43     for (int i = 1; i < 1000; i++) {
44       LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(i).build(loader);
45       assertEquals(i, CacheTesting.getTotalSegmentSize(cache));
46     }
47   }
48 
testEviction_setMaxSegmentWeight()49   public void testEviction_setMaxSegmentWeight() {
50     IdentityLoader<Object> loader = identityLoader();
51     for (int i = 1; i < 1000; i++) {
52       LoadingCache<Object, Object> cache =
53           CacheBuilder.newBuilder().maximumWeight(i).weigher(constantWeigher(1)).build(loader);
54       assertEquals(i, CacheTesting.getTotalSegmentSize(cache));
55     }
56   }
57 
testEviction_maxSizeOneSegment()58   public void testEviction_maxSizeOneSegment() {
59     IdentityLoader<Integer> loader = identityLoader();
60     LoadingCache<Integer, Integer> cache =
61         CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(MAX_SIZE).build(loader);
62     for (int i = 0; i < 2 * MAX_SIZE; i++) {
63       cache.getUnchecked(i);
64       assertEquals(Math.min(i + 1, MAX_SIZE), cache.size());
65     }
66 
67     assertEquals(MAX_SIZE, cache.size());
68     CacheTesting.checkValidState(cache);
69   }
70 
testEviction_maxWeightOneSegment()71   public void testEviction_maxWeightOneSegment() {
72     IdentityLoader<Integer> loader = identityLoader();
73     LoadingCache<Integer, Integer> cache =
74         CacheBuilder.newBuilder()
75             .concurrencyLevel(1)
76             .maximumWeight(2 * MAX_SIZE)
77             .weigher(constantWeigher(2))
78             .build(loader);
79     for (int i = 0; i < 2 * MAX_SIZE; i++) {
80       cache.getUnchecked(i);
81       assertEquals(Math.min(i + 1, MAX_SIZE), cache.size());
82     }
83 
84     assertEquals(MAX_SIZE, cache.size());
85     CacheTesting.checkValidState(cache);
86   }
87 
testEviction_maxSize()88   public void testEviction_maxSize() {
89     CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener();
90     IdentityLoader<Integer> loader = identityLoader();
91     LoadingCache<Integer, Integer> cache =
92         CacheBuilder.newBuilder()
93             .maximumSize(MAX_SIZE)
94             .removalListener(removalListener)
95             .build(loader);
96     for (int i = 0; i < 2 * MAX_SIZE; i++) {
97       cache.getUnchecked(i);
98       assertTrue(cache.size() <= MAX_SIZE);
99     }
100 
101     assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache));
102     assertEquals(MAX_SIZE, cache.size());
103     CacheTesting.processPendingNotifications(cache);
104     assertEquals(MAX_SIZE, removalListener.getCount());
105     CacheTesting.checkValidState(cache);
106   }
107 
testEviction_maxWeight()108   public void testEviction_maxWeight() {
109     CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener();
110     IdentityLoader<Integer> loader = identityLoader();
111     LoadingCache<Integer, Integer> cache =
112         CacheBuilder.newBuilder()
113             .maximumWeight(2 * MAX_SIZE)
114             .weigher(constantWeigher(2))
115             .removalListener(removalListener)
116             .build(loader);
117     for (int i = 0; i < 2 * MAX_SIZE; i++) {
118       cache.getUnchecked(i);
119       assertTrue(cache.size() <= MAX_SIZE);
120     }
121 
122     assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache));
123     assertEquals(MAX_SIZE, cache.size());
124     CacheTesting.processPendingNotifications(cache);
125     assertEquals(MAX_SIZE, removalListener.getCount());
126     CacheTesting.checkValidState(cache);
127   }
128 
129   /**
130    * With an unlimited-size cache with maxWeight of 0, entries weighing 0 should still be cached.
131    * Entries with positive weight should not be cached (nor dump existing cache).
132    */
testEviction_maxWeight_zero()133   public void testEviction_maxWeight_zero() {
134     CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener();
135     IdentityLoader<Integer> loader = identityLoader();
136 
137     // Even numbers are free, odd are too expensive
138     Weigher<Integer, Integer> evensOnly =
139         new Weigher<Integer, Integer>() {
140           @Override
141           public int weigh(Integer k, Integer v) {
142             return k % 2;
143           }
144         };
145 
146     LoadingCache<Integer, Integer> cache =
147         CacheBuilder.newBuilder()
148             .concurrencyLevel(1)
149             .maximumWeight(0)
150             .weigher(evensOnly)
151             .removalListener(removalListener)
152             .build(loader);
153 
154     // 1 won't be cached
155     assertThat(cache.getUnchecked(1)).isEqualTo(1);
156     assertThat(cache.asMap().keySet()).isEmpty();
157 
158     CacheTesting.processPendingNotifications(cache);
159     assertThat(removalListener.getCount()).isEqualTo(1);
160 
161     // 2 will be cached
162     assertThat(cache.getUnchecked(2)).isEqualTo(2);
163     assertThat(cache.asMap().keySet()).containsExactly(2);
164 
165     CacheTesting.processPendingNotifications(cache);
166     CacheTesting.checkValidState(cache);
167     assertThat(removalListener.getCount()).isEqualTo(1);
168 
169     // 4 will be cached
170     assertThat(cache.getUnchecked(4)).isEqualTo(4);
171     assertThat(cache.asMap().keySet()).containsExactly(2, 4);
172 
173     CacheTesting.processPendingNotifications(cache);
174     assertThat(removalListener.getCount()).isEqualTo(1);
175 
176     // 5 won't be cached, won't dump cache
177     assertThat(cache.getUnchecked(5)).isEqualTo(5);
178     assertThat(cache.asMap().keySet()).containsExactly(2, 4);
179 
180     CacheTesting.processPendingNotifications(cache);
181     assertThat(removalListener.getCount()).isEqualTo(2);
182 
183     // Should we pepper more of these calls throughout the above? Where?
184     CacheTesting.checkValidState(cache);
185   }
186 
187   /**
188    * Tests that when a single entry exceeds the segment's max weight, the new entry is immediately
189    * evicted and nothing else.
190    */
testEviction_maxWeight_entryTooBig()191   public void testEviction_maxWeight_entryTooBig() {
192     CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener();
193     IdentityLoader<Integer> loader = identityLoader();
194 
195     LoadingCache<Integer, Integer> cache =
196         CacheBuilder.newBuilder()
197             .concurrencyLevel(1)
198             .maximumWeight(4)
199             .weigher(intValueWeigher())
200             .removalListener(removalListener)
201             .build(loader);
202 
203     // caches 2
204     assertThat(cache.getUnchecked(2)).isEqualTo(2);
205     assertThat(cache.asMap().keySet()).containsExactly(2);
206 
207     CacheTesting.processPendingNotifications(cache);
208     assertThat(removalListener.getCount()).isEqualTo(0);
209 
210     // caches 3, evicts 2
211     assertThat(cache.getUnchecked(3)).isEqualTo(3);
212     assertThat(cache.asMap().keySet()).containsExactly(3);
213 
214     CacheTesting.processPendingNotifications(cache);
215     assertThat(removalListener.getCount()).isEqualTo(1);
216 
217     // doesn't cache 5, doesn't evict
218     assertThat(cache.getUnchecked(5)).isEqualTo(5);
219     assertThat(cache.asMap().keySet()).containsExactly(3);
220 
221     CacheTesting.processPendingNotifications(cache);
222     assertThat(removalListener.getCount()).isEqualTo(2);
223 
224     // caches 1, evicts nothing
225     assertThat(cache.getUnchecked(1)).isEqualTo(1);
226     assertThat(cache.asMap().keySet()).containsExactly(3, 1);
227 
228     CacheTesting.processPendingNotifications(cache);
229     assertThat(removalListener.getCount()).isEqualTo(2);
230 
231     // caches 4, evicts 1 and 3
232     assertThat(cache.getUnchecked(4)).isEqualTo(4);
233     assertThat(cache.asMap().keySet()).containsExactly(4);
234 
235     CacheTesting.processPendingNotifications(cache);
236     assertThat(removalListener.getCount()).isEqualTo(4);
237 
238     // Should we pepper more of these calls throughout the above? Where?
239     CacheTesting.checkValidState(cache);
240   }
241 
testEviction_overflow()242   public void testEviction_overflow() {
243     CountingRemovalListener<Object, Object> removalListener = countingRemovalListener();
244     IdentityLoader<Object> loader = identityLoader();
245     LoadingCache<Object, Object> cache =
246         CacheBuilder.newBuilder()
247             .concurrencyLevel(1)
248             .maximumWeight(1L << 31)
249             .weigher(constantWeigher(Integer.MAX_VALUE))
250             .removalListener(removalListener)
251             .build(loader);
252     cache.getUnchecked(objectWithHash(0));
253     cache.getUnchecked(objectWithHash(0));
254     CacheTesting.processPendingNotifications(cache);
255     assertEquals(1, removalListener.getCount());
256   }
257 
testUpdateRecency_onGet()258   public void testUpdateRecency_onGet() {
259     IdentityLoader<Integer> loader = identityLoader();
260     final LoadingCache<Integer, Integer> cache =
261         CacheBuilder.newBuilder().maximumSize(MAX_SIZE).build(loader);
262     CacheTesting.checkRecency(
263         cache,
264         MAX_SIZE,
265         new Receiver<ReferenceEntry<Integer, Integer>>() {
266           @Override
267           public void accept(ReferenceEntry<Integer, Integer> entry) {
268             cache.getUnchecked(entry.getKey());
269           }
270         });
271   }
272 
testUpdateRecency_onInvalidate()273   public void testUpdateRecency_onInvalidate() {
274     IdentityLoader<Integer> loader = identityLoader();
275     final LoadingCache<Integer, Integer> cache =
276         CacheBuilder.newBuilder().maximumSize(MAX_SIZE).concurrencyLevel(1).build(loader);
277     CacheTesting.checkRecency(
278         cache,
279         MAX_SIZE,
280         new Receiver<ReferenceEntry<Integer, Integer>>() {
281           @Override
282           public void accept(ReferenceEntry<Integer, Integer> entry) {
283             Integer key = entry.getKey();
284             cache.invalidate(key);
285           }
286         });
287   }
288 
testEviction_lru()289   public void testEviction_lru() {
290     // test lru within a single segment
291     IdentityLoader<Integer> loader = identityLoader();
292     LoadingCache<Integer, Integer> cache =
293         CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(10).build(loader);
294     CacheTesting.warmUp(cache, 0, 10);
295     Set<Integer> keySet = cache.asMap().keySet();
296     assertThat(keySet).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
297 
298     // re-order
299     getAll(cache, asList(0, 1, 2));
300     CacheTesting.drainRecencyQueues(cache);
301     assertThat(keySet).containsExactly(3, 4, 5, 6, 7, 8, 9, 0, 1, 2);
302 
303     // evict 3, 4, 5
304     getAll(cache, asList(10, 11, 12));
305     CacheTesting.drainRecencyQueues(cache);
306     assertThat(keySet).containsExactly(6, 7, 8, 9, 0, 1, 2, 10, 11, 12);
307 
308     // re-order
309     getAll(cache, asList(6, 7, 8));
310     CacheTesting.drainRecencyQueues(cache);
311     assertThat(keySet).containsExactly(9, 0, 1, 2, 10, 11, 12, 6, 7, 8);
312 
313     // evict 9, 0, 1
314     getAll(cache, asList(13, 14, 15));
315     CacheTesting.drainRecencyQueues(cache);
316     assertThat(keySet).containsExactly(2, 10, 11, 12, 6, 7, 8, 13, 14, 15);
317   }
318 
testEviction_weightedLru()319   public void testEviction_weightedLru() {
320     // test weighted lru within a single segment
321     IdentityLoader<Integer> loader = identityLoader();
322     LoadingCache<Integer, Integer> cache =
323         CacheBuilder.newBuilder()
324             .concurrencyLevel(1)
325             .maximumWeight(45)
326             .weigher(intKeyWeigher())
327             .build(loader);
328     CacheTesting.warmUp(cache, 0, 10);
329     Set<Integer> keySet = cache.asMap().keySet();
330     assertThat(keySet).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
331 
332     // re-order
333     getAll(cache, asList(0, 1, 2));
334     CacheTesting.drainRecencyQueues(cache);
335     assertThat(keySet).containsExactly(3, 4, 5, 6, 7, 8, 9, 0, 1, 2);
336 
337     // evict 3, 4, 5
338     getAll(cache, asList(10));
339     CacheTesting.drainRecencyQueues(cache);
340     assertThat(keySet).containsExactly(6, 7, 8, 9, 0, 1, 2, 10);
341 
342     // re-order
343     getAll(cache, asList(6, 7, 8));
344     CacheTesting.drainRecencyQueues(cache);
345     assertThat(keySet).containsExactly(9, 0, 1, 2, 10, 6, 7, 8);
346 
347     // evict 9, 1, 2, 10
348     getAll(cache, asList(15));
349     CacheTesting.drainRecencyQueues(cache);
350     assertThat(keySet).containsExactly(0, 6, 7, 8, 15);
351 
352     // fill empty space
353     getAll(cache, asList(9));
354     CacheTesting.drainRecencyQueues(cache);
355     assertThat(keySet).containsExactly(0, 6, 7, 8, 15, 9);
356 
357     // evict 6
358     getAll(cache, asList(1));
359     CacheTesting.drainRecencyQueues(cache);
360     assertThat(keySet).containsExactly(0, 7, 8, 15, 9, 1);
361   }
362 
testEviction_overweight()363   public void testEviction_overweight() {
364     // test weighted lru within a single segment
365     IdentityLoader<Integer> loader = identityLoader();
366     LoadingCache<Integer, Integer> cache =
367         CacheBuilder.newBuilder()
368             .concurrencyLevel(1)
369             .maximumWeight(45)
370             .weigher(intKeyWeigher())
371             .build(loader);
372     CacheTesting.warmUp(cache, 0, 10);
373     Set<Integer> keySet = cache.asMap().keySet();
374     assertThat(keySet).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
375 
376     // add an at-the-maximum-weight entry
377     getAll(cache, asList(45));
378     CacheTesting.drainRecencyQueues(cache);
379     assertThat(keySet).containsExactly(0, 45);
380 
381     // add an over-the-maximum-weight entry
382     getAll(cache, asList(46));
383     CacheTesting.drainRecencyQueues(cache);
384     assertThat(keySet).contains(0);
385   }
386 
testEviction_invalidateAll()387   public void testEviction_invalidateAll() {
388     // test that .invalidateAll() resets total weight state correctly
389     IdentityLoader<Integer> loader = identityLoader();
390     LoadingCache<Integer, Integer> cache =
391         CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(10).build(loader);
392 
393     Set<Integer> keySet = cache.asMap().keySet();
394     assertThat(keySet).isEmpty();
395 
396     // add 0, 1, 2, 3, 4
397     getAll(cache, asList(0, 1, 2, 3, 4));
398     CacheTesting.drainRecencyQueues(cache);
399     assertThat(keySet).containsExactly(0, 1, 2, 3, 4);
400 
401     // invalidate all
402     cache.invalidateAll();
403     CacheTesting.drainRecencyQueues(cache);
404     assertThat(keySet).isEmpty();
405 
406     // add 5, 6, 7, 8, 9, 10, 11, 12
407     getAll(cache, asList(5, 6, 7, 8, 9, 10, 11, 12));
408     CacheTesting.drainRecencyQueues(cache);
409     assertThat(keySet).containsExactly(5, 6, 7, 8, 9, 10, 11, 12);
410   }
411 
getAll(LoadingCache<Integer, Integer> cache, List<Integer> keys)412   private static void getAll(LoadingCache<Integer, Integer> cache, List<Integer> keys) {
413     for (int i : keys) {
414       cache.getUnchecked(i);
415     }
416   }
417 
objectWithHash(final int hash)418   private Object objectWithHash(final int hash) {
419     return new Object() {
420       @Override
421       public int hashCode() {
422         return hash;
423       }
424     };
425   }
426 }
427