View Javadoc
1   package com.atlassian.cache;
2   
3   import java.util.concurrent.CountDownLatch;
4   import java.util.concurrent.TimeUnit;
5   import java.util.concurrent.atomic.AtomicInteger;
6   
7   import javax.annotation.Nonnull;
8   import javax.annotation.Nullable;
9   
10  import com.atlassian.utt.concurrency.Barrier;
11  import com.atlassian.utt.concurrency.TestThread;
12  
13  import com.google.common.base.Function;
14  
15  import org.junit.Test;
16  
17  import static com.atlassian.utt.concurrency.TestThread.runTest;
18  import static org.hamcrest.Matchers.is;
19  import static org.hamcrest.Matchers.isOneOf;
20  import static org.hamcrest.Matchers.notNullValue;
21  import static org.hamcrest.core.IsEqual.equalTo;
22  import static org.junit.Assert.assertNotSame;
23  import static org.junit.Assert.assertThat;
24  import static org.junit.Assert.fail;
25  
26  /**
27   * Test the Lazy Cache
28   *
29   * @since 2.0
30   */
31  public abstract class AbstractCacheLazyTest extends AbstractCacheTest
32  {
33      public AbstractCacheLazyTest()
34      {
35      }
36  
37      public AbstractCacheLazyTest(CacheType cacheType)
38      {
39          super(cacheType);
40      }
41  
42      @Test
43      public void testGetName() throws Exception
44      {
45          Cache<String, Long> cache = makeUnexpiringCache();
46  
47          assertThat(cache.getName(), equalTo("mycache"));
48      }
49  
50      @Test
51      public void testFactoryGeneratedName() throws Exception
52      {
53          Cache<String, Long> cache = factory.getCache(Object.class, "mycache");
54  
55          assertThat(cache.getName(), equalTo("java.lang.Object.mycache"));
56      }
57  
58      @Test
59      public void testGetKeys() throws Exception
60      {
61          Cache<String, Long> cache = makeUnexpiringCache();
62          // Add some entries using the builder
63          cache.get("1");
64          cache.get("2");
65          cache.get("3");
66          cache.get("4");
67          assertThat(cache.get("1"), equalTo(1L));
68          assertThat(cache.get("2"), equalTo(2L));
69          assertThat(cache.get("3"), equalTo(3L));
70          assertThat(cache.get("4"), equalTo(4L));
71          assertSize(cache, 4);
72      }
73  
74      @Test
75      public void testConstructExpiringCache() throws Exception
76      {
77          Cache<String, Long> cache = makeExpiringCache();
78          // Add some entries using the builder
79          cache.get("1");
80          cache.get("2");
81          cache.get("3");
82          cache.get("4");
83          assertThat(cache.get("1"), equalTo(1L));
84          assertThat(cache.get("2"), equalTo(2L));
85          assertThat(cache.get("3"), equalTo(3L));
86          assertThat(cache.get("4"), equalTo(4L));
87          assertSize(cache, 4);
88      }
89  
90      @Test
91      public void testPut() throws Exception
92      {
93          Cache<String, Long> cache = makeUnexpiringCache();
94          // Add some entries using the builder
95          cache.put("1", 11L);
96          cache.put("2", 12L);
97          cache.put("3", 13L);
98          cache.put("4", 14L);
99          assertThat(cache.get("1"), equalTo(11L));
100         assertThat(cache.get("2"), equalTo(12L));
101         assertThat(cache.get("3"), equalTo(13L));
102         assertThat(cache.get("4"), equalTo(14L));
103         assertSize(cache, (4));
104     }
105 
106     @Test
107     public void testGet() throws Exception
108     {
109         Cache<String, Long> cache = makeUnexpiringCache();
110         // Add some entries using the builder
111         cache.put("1", 11L);
112         cache.put("2", 12L);
113         cache.put("3", 13L);
114         cache.put("4", 14L);
115         assertThat(cache.get("1"), equalTo(11L));
116         assertThat(cache.get("2"), equalTo(12L));
117         assertThat(cache.get("3"), equalTo(13L));
118         assertThat(cache.get("4"), equalTo(14L));
119         assertThat(cache.get("5"), equalTo(5L));
120         assertThat(cache.get("6"), equalTo(6L));
121         assertThat(cache.get("7"), equalTo(7L));
122         assertSize(cache, (7));
123     }
124 
125     @Test
126     public void testRemove() throws Exception
127     {
128         Cache<String, Long> cache = makeUnexpiringCache();
129         // Add some entries using the builder
130         cache.put("1", 11L);
131         cache.put("2", 12L);
132         cache.put("3", 13L);
133         cache.put("4", 14L);
134         assertThat(cache.get("1"), equalTo(11L));
135         assertThat(cache.get("2"), equalTo(12L));
136         assertThat(cache.get("3"), equalTo(13L));
137         assertThat(cache.get("4"), equalTo(14L));
138         assertSize(cache, (4));
139 
140         cache.remove("1");
141         cache.remove("2");
142         cache.remove("3");
143         assertSize(cache, (1));
144         assertThat(cache.containsKey("1"), is(false));
145         assertThat(cache.containsKey("2"), is(false));
146         assertThat(cache.containsKey("3"), is(false));
147 
148         // Removed values should be recomputed
149         assertThat(cache.get("1"), equalTo(1L));
150         assertThat(cache.get("2"), equalTo(2L));
151         assertThat(cache.get("3"), equalTo(3L));
152         assertThat(cache.get("4"), equalTo(14L));
153         assertSize(cache, (4));
154     }
155 
156     @Test
157     public void testRemoveTwice() throws Exception
158     {
159         Cache<String, Long> cache = makeUnexpiringCache();
160 
161         // Add some entries using the builder
162         cache.put("1", 11L);
163         cache.put("2", 12L);
164         cache.put("3", 13L);
165         cache.put("4", 14L);
166         assertThat(cache.get("1"), equalTo(11L));
167         assertThat(cache.get("2"), equalTo(12L));
168         assertThat(cache.get("3"), equalTo(13L));
169         assertThat(cache.get("4"), equalTo(14L));
170         assertSize(cache, (4));
171 
172         cache.remove("1");
173         // Remove again should be a no-op
174         cache.remove("1");
175         assertSize(cache, (3));
176 
177         // Removed values should be recomputed
178         assertThat(cache.get("1"), equalTo(1L));
179         assertThat(cache.get("2"), equalTo(12L));
180         assertThat(cache.get("3"), equalTo(13L));
181         assertThat(cache.get("4"), equalTo(14L));
182         assertSize(cache, (4));
183     }
184 
185     @Test
186     public void testRemoveAll() throws Exception
187     {
188         Cache<String, Long> cache = makeUnexpiringCache();
189         // Add some entries using the builder
190         cache.put("1", 11L);
191         cache.put("2", 12L);
192         cache.put("3", 13L);
193         cache.put("4", 14L);
194         assertThat(cache.get("1"), equalTo(11L));
195         assertThat(cache.get("2"), equalTo(12L));
196         assertThat(cache.get("3"), equalTo(13L));
197         assertThat(cache.get("4"), equalTo(14L));
198         assertSize(cache, (4));
199 
200         cache.removeAll();
201         assertEmpty(cache);
202 
203         // Removed values should be recomputed
204         assertThat(cache.get("1"), equalTo(1L));
205         assertThat(cache.get("2"), equalTo(2L));
206         assertThat(cache.get("3"), equalTo(3L));
207         assertThat(cache.get("4"), equalTo(4L));
208         assertSize(cache, (4));
209     }
210 
211     @Test
212     public void testClear() throws Exception
213     {
214         Cache<String, Long> cache = makeUnexpiringCache();
215         // Add some entries using the builder
216         cache.put("1", 11L);
217         cache.put("2", 12L);
218         cache.put("3", 13L);
219         cache.put("4", 14L);
220         assertThat(cache.get("1"), equalTo(11L));
221         assertThat(cache.get("2"), equalTo(12L));
222         assertThat(cache.get("3"), equalTo(13L));
223         assertThat(cache.get("4"), equalTo(14L));
224         assertSize(cache, (4));
225 
226         ((ManagedCache) cache).clear();
227         assertMissingKeys(cache, "1", "2", "3", "4");
228         assertEmpty(cache);
229 
230         // Removed values should be recomputed
231         assertThat(cache.get("1"), equalTo(1L));
232         assertThat(cache.get("2"), equalTo(2L));
233         assertThat(cache.get("3"), equalTo(3L));
234         assertThat(cache.get("4"), equalTo(4L));
235         assertSize(cache, (4));
236     }
237 
238     @Test(expected = CacheException.class)
239     public void testNullKey() throws Exception
240     {
241         makeNullReturningCache().get(null);
242     }
243 
244     @Test(expected = CacheException.class)
245     public void testNullValue() throws Exception
246     {
247         Cache<String, Long> cache = makeNullReturningCache();
248 
249         // Add some entries using the builder
250         assertThat(cache.get("1"), equalTo(1L));
251         assertThat(cache.get("2"), equalTo(2L));
252         assertThat(cache.get("3"), equalTo(3L));
253 
254         cache.get("George");
255     }
256 
257     @Test
258     public void testMaxEntries() throws Exception
259     {
260         final AtomicInteger loadCounter = new AtomicInteger();
261         Cache<String, Long> cache = makeSizeLimitedCache(3, loadCounter);
262 
263         // Add some entries using the builder
264         assertThat(cache.get("1"), equalTo(1L));
265         assertThat(cache.get("1"), equalTo(1L));
266         assertThat(cache.get("2"), equalTo(2L));
267         assertThat(cache.get("3"), equalTo(3L));
268         assertThat("loadCounter", loadCounter.get(), equalTo(3));
269         assertSize(cache, (3));
270         assertThat(cache.get("3"), equalTo(3L));
271         assertThat(cache.get("3"), equalTo(3L));
272         assertThat("loadCounter", loadCounter.get(), equalTo(3));
273         assertSize(cache, (3));
274         assertThat(cache.get("4"), equalTo(4L));
275         assertThat("loadCounter", loadCounter.get(), equalTo(4));
276         assertSize(cache, (3));
277     }
278 
279     @Test
280     public void testExceptionHandling() throws Exception
281     {
282         Cache<String, Long> cache = makeExceptionalCache();
283 
284         // Add some entries using the builder
285         assertThat(cache.get("1"), equalTo(1L));
286         assertThat(cache.get("2"), equalTo(2L));
287         assertThat(cache.get("3"), equalTo(3L));
288         try
289         {
290             cache.get("George");
291             fail("Should throw CacheException when the loader throws an exception");
292         }
293         catch (CacheException ex)
294         {
295             assertThat("This exception should wrap the original Exception", ex.getCause(), notNullValue());
296         }
297         try
298         {
299             Long v = cache.get(null);
300             fail("Should throw CacheException when the Key is a null");
301         }
302         catch (CacheException ex)
303         {
304             // Just want a CacheException
305         }
306         assertSize(cache, (3));
307     }
308 
309     @Test
310     public void testNewInstanceForEveryGet()
311     {
312         Cache<String, Long> cache1 = makeUnexpiringCache();
313         Cache<String, Long> cache2 = makeUnexpiringCache();
314 
315         assertNotSame("The cache manager should not return the same cache twice", cache1, cache2);
316     }
317 
318     protected static final Function<Cache<String, Integer>, Void> REMOVE_0 = new Function<Cache<String, Integer>, Void>()
319     {
320         @Nullable
321         @Override
322         public Void apply(Cache<String, Integer> input)
323         {
324             input.remove("key");
325             return null;
326         }
327     };
328 
329     protected static final Function<Cache<String, Integer>, Void> REMOVE_ALL = new Function<Cache<String, Integer>, Void>()
330     {
331         @Nullable
332         @Override
333         public Void apply(Cache<String, Integer> input)
334         {
335             input.removeAll();
336             return null;
337         }
338     };
339 
340     @Test
341     public void testRemoveConcurrentWithLoaderLocal()
342     {
343         removeConcurrentWithLoader(factory, null, REMOVE_0);
344     }
345 
346     @Test
347     public void testRemoveAllConcurrentWithLoaderLocal()
348     {
349         removeConcurrentWithLoader(factory, null, REMOVE_ALL);
350     }
351 
352     protected void removeConcurrentWithLoader(final CacheFactory factory1, @Nullable final CacheFactory factory2,
353                                               final Function<Cache<String, Integer>, Void> removeFn)
354     {
355         final Barrier doUpdate = new Barrier();
356         final CountDownLatch removeCalled = new CountDownLatch(1);
357         final Barrier afterGet = new Barrier();
358         final AtomicInteger dbValue = new AtomicInteger(1);
359 
360         final CacheLoader<String, Integer> loader = new CacheLoader<String, Integer>()
361         {
362             @Nonnull
363             @Override
364             public Integer load(@Nonnull String ignored)
365             {
366                 final int value = dbValue.get();
367                 doUpdate.trySignal();
368                 try
369                 {
370                     // Whether or not remove gets blocked by the load operation is implementation-dependent.
371                     removeCalled.await(250L, TimeUnit.MILLISECONDS);
372                 }
373                 catch (InterruptedException ie)
374                 {
375                     throw new AssertionError(ie);
376                 }
377                 afterGet.trySignal();
378                 return value;
379             }
380         };
381 
382         final Cache<String, Integer> cache1 = factory1.getCache("removeConcurrentWithLoader", loader,
383                 settingsBuilder().build());
384 
385         final Cache<String, Integer> cache2 = (factory2 != null)
386                 ? factory2.getCache("removeConcurrentWithLoader", loader, settingsBuilder().build())
387                 : cache1;
388 
389         // Make sure everything is live.  Hazelcast takes >5s to initialize sometimes.
390         cache1.removeAll();
391         cache2.removeAll();
392 
393         final TestThread blockingGet = new TestThread("blockingGet")
394         {
395             @Override
396             protected void go() throws Exception
397             {
398                 assertThat(cache1.get("key"), isOneOf(1, 2));  // Either the stale or updated value is acceptable
399                 removeCalled.await();
400                 assertEventuallyThat(() -> cache1.get("key"), is(2));
401             }
402         };
403 
404         final TestThread modifyRemoveGet = new TestThread("modifyRemoveGet")
405         {
406             @Override
407             protected void go() throws Exception
408             {
409                 doUpdate.await();
410                 dbValue.set(2);
411                 removeFn.apply(cache2);
412                 removeCalled.countDown();
413                 assertEventuallyThat(() -> cache2.get("key"), is(2));
414             }
415         };
416 
417         runTest(blockingGet, modifyRemoveGet);
418     }
419 
420     @Test
421     public void testRemoveConcurrentWithSupplierLocal()
422     {
423         removeConcurrentWithSupplier(factory, null, REMOVE_0);
424     }
425 
426     @Test
427     public void testRemoveAllConcurrentWithSupplierLocal()
428     {
429         removeConcurrentWithSupplier(factory, null, REMOVE_ALL);
430     }
431 
432     protected void removeConcurrentWithSupplier(final CacheFactory factory1, @Nullable final CacheFactory factory2,
433                                                 final Function<Cache<String, Integer>, Void> removeFn)
434     {
435         final Barrier doUpdate = new Barrier();
436         final CountDownLatch removeCalled = new CountDownLatch(1);
437         final Barrier afterGet = new Barrier();
438         final AtomicInteger dbValue = new AtomicInteger(1);
439 
440         final Supplier<Integer> supplier = () -> {
441             final int value = dbValue.get();
442             doUpdate.trySignal();
443             try
444             {
445                 // Whether or not remove gets blocked by the load operation is implementation-dependent.
446                 removeCalled.await(250L, TimeUnit.MILLISECONDS);
447             }
448             catch (InterruptedException ie)
449             {
450                 throw new AssertionError(ie);
451             }
452             afterGet.trySignal();
453             return value;
454         };
455 
456         final Cache<String, Integer> cache1 = factory1.getCache("removeConcurrentWithLoader", null,
457                 settingsBuilder().build());
458 
459         final Cache<String, Integer> cache2 = (factory2 != null)
460                 ? factory2.getCache("removeConcurrentWithLoader", null, settingsBuilder().build())
461                 : cache1;
462 
463         // Make sure everything is live.  Hazelcast takes >5s to initialize sometimes.
464         cache1.removeAll();
465         cache2.removeAll();
466 
467         final TestThread blockingGet = new TestThread("blockingGet")
468         {
469             @Override
470             protected void go() throws Exception
471             {
472                 assertThat(cache1.get("key", supplier), isOneOf(1, 2));  // Either the stale or updated value is acceptable
473                 removeCalled.await();
474                 assertThat(cache1.get("key", supplier), is(2));
475             }
476         };
477 
478         final TestThread modifyRemoveGet = new TestThread("modifyRemoveGet")
479         {
480             @Override
481             protected void go() throws Exception
482             {
483                 doUpdate.await();
484                 dbValue.set(2);
485                 removeFn.apply(cache2);
486                 removeCalled.countDown();
487                 assertThat(cache2.get("key", supplier), is(2));
488             }
489         };
490 
491         runTest(blockingGet, modifyRemoveGet);
492     }
493 }