1 package com.atlassian.cache;
2
3 import java.util.concurrent.Callable;
4 import java.util.concurrent.CountDownLatch;
5 import java.util.concurrent.ExecutionException;
6 import java.util.concurrent.ExecutorService;
7 import java.util.concurrent.Executors;
8 import java.util.concurrent.Future;
9 import java.util.concurrent.TimeUnit;
10 import java.util.concurrent.TimeoutException;
11 import java.util.concurrent.atomic.AtomicBoolean;
12 import java.util.concurrent.atomic.AtomicInteger;
13 import java.util.concurrent.atomic.AtomicLong;
14 import javax.annotation.Nonnull;
15
16 import org.hamcrest.Matchers;
17 import org.junit.Test;
18
19 import static org.hamcrest.Matchers.hasSize;
20 import static org.hamcrest.Matchers.is;
21 import static org.junit.Assert.assertThat;
22
23 public abstract class AbstractCacheTest
24 {
25 @Test
26 public void testRemoveFromCacheWhileLoadingCacheValueReturnsLatestValue()
27 throws InterruptedException, TimeoutException, ExecutionException
28 {
29 testWhenCacheValueManipulatedNewValueReturned(new CacheManipulator<String, Long>()
30 {
31 @Override
32 public void manipulateKeyOn(final String key, final Cache<String, Long> cache)
33 {
34 cache.remove(key);
35 }
36 });
37 }
38
39
40
41
42
43
44
45
46
47
48 @Test
49 public void testRemoveSpecificValueFromCacheWhileLoadingCacheValueReturnsLatestValue()
50 throws InterruptedException, TimeoutException, ExecutionException
51 {
52 testWhenCacheValueManipulatedNewValueReturned(new CacheManipulator<String, Long>()
53 {
54 @Override
55 public void manipulateKeyOn(final String key, final Cache<String, Long> cache)
56 {
57
58
59 cache.remove(key, 1L);
60 }
61 });
62 }
63
64 protected CacheFactory factory;
65
66 protected Cache<String,Long> makeExceptionalCache()
67 {
68
69 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
70 {
71 @Nonnull
72 @Override
73 public Long load(@Nonnull final String key)
74 {
75 return Long.valueOf(key);
76 }
77 };
78 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
79 assertEmpty(cache);
80 return cache;
81 }
82
83 protected Cache<String, Long> makeExpiringCache()
84 {
85
86 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
87 {
88 @Nonnull
89 @Override
90 public Long load(@Nonnull final String key)
91 {
92 try
93 {
94 return Long.valueOf(key);
95 }
96 catch (NumberFormatException e)
97 {
98 return -21L;
99 }
100 }
101 };
102 CacheSettings settings = settingsBuilder().expireAfterAccess(10, TimeUnit.MILLISECONDS).build();
103 final Cache<String, Long> cache = factory.getCache("mycache", loader, settings);
104 assertEmpty(cache);
105 return cache;
106 }
107
108 protected Cache<String,Long> makeNullReturningCache()
109 {
110
111 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
112 {
113 @Nonnull
114 @Override
115 public Long load(@Nonnull final String key)
116 {
117 try
118 {
119 return Long.valueOf(key);
120 }
121 catch (NumberFormatException e)
122 {
123 return null;
124 }
125 }
126 };
127 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
128 assertEmpty(cache);
129 return cache;
130 }
131
132 protected Cache<String, Long> makeSimpleCache()
133 {
134
135 final Cache<String, Long> cache = factory.getCache("mycache", null, settingsBuilder().build());
136 assertEmpty(cache);
137 return cache;
138 }
139
140 protected Cache<String, Long> makeSizeLimitedCache(int maxEntries)
141 {
142 CacheSettings required = settingsBuilder().maxEntries(maxEntries).build();
143 final Cache<String, Long> cache = factory.getCache("mycache", null, required);
144 assertEmpty(cache);
145 return cache;
146 }
147
148 protected Cache<String, Long> makeSizeLimitedCache(final int maxEntries, final AtomicInteger loadCounter)
149 {
150
151 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
152 {
153 @Nonnull
154 @Override
155 public Long load(@Nonnull final String key)
156 {
157 loadCounter.incrementAndGet();
158 return Long.valueOf(key);
159 }
160 };
161 CacheSettings settings = settingsBuilder().maxEntries(maxEntries).build();
162 final Cache<String, Long> cache = factory.getCache("mycache", loader, settings);
163 assertEmpty(cache);
164 return cache;
165 }
166
167 protected Cache<String, Long> makeUnexpiringCache()
168 {
169
170 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
171 {
172 @Nonnull
173 @Override
174 public Long load(@Nonnull final String key)
175 {
176 try
177 {
178 return Long.valueOf(key);
179 }
180 catch (NumberFormatException e)
181 {
182 return -21L;
183 }
184 }
185 };
186 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
187 assertEmpty(cache);
188 return cache;
189 }
190
191 protected CacheSettingsBuilder settingsBuilder()
192 {
193 return new CacheSettingsBuilder();
194 }
195
196
197
198 protected static <K,V> void assertEmpty(Cache<K,V> cache)
199 {
200 assertThat(cache.getKeys(), Matchers.<K>empty());
201 }
202
203 protected static <K,V> void assertSize(Cache<K,V> cache, final int expectedSize)
204 {
205 assertThat(cache.getKeys(), hasSize(expectedSize));
206 }
207
208 protected Cache<String, Long> makeCacheUsingLoader(final CacheLoader<String, Long> loader)
209 {
210 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
211 return cache;
212 }
213
214 private void testWhenCacheValueManipulatedNewValueReturned(final CacheManipulator<String, Long> remover)
215 throws InterruptedException, TimeoutException, ExecutionException
216 {
217 final CountDownLatch readyToRemove = new CountDownLatch(1);
218 final CountDownLatch hasStartedRemove = new CountDownLatch(1);
219 final AtomicLong cacheValue = new AtomicLong(1L);
220 final AtomicBoolean loaderHasReturned = new AtomicBoolean();
221 final String key = "keyvalue";
222
223 final CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
224 {
225 @Nonnull
226 @Override
227 public Long load(@Nonnull final String key)
228 {
229 try
230 {
231
232
233 Long value = cacheValue.get();
234 readyToRemove.countDown();
235
236
237
238
239
240
241 hasStartedRemove.await();
242
243 Thread.sleep(200l);
244 loaderHasReturned.set(true);
245 return value;
246 }
247 catch (InterruptedException e)
248 {
249 throw new RuntimeException(e);
250 }
251 }
252 };
253
254 final Cache<String, Long> pausingCache = makeCacheUsingLoader(loader);
255
256 ExecutorService service = Executors.newFixedThreadPool(2);
257 Future<Long> f = service.submit(new Callable<Long>()
258 {
259 @Override
260 public Long call() throws Exception
261 {
262 return pausingCache.get(key);
263 }
264 });
265
266 readyToRemove.await();
267 Long newCacheValue = cacheValue.incrementAndGet();
268
269 Future<Void> manipulationOp = service.submit(new Callable<Void>()
270 {
271 @Override
272 public Void call() throws Exception
273 {
274
275 hasStartedRemove.countDown();
276 remover.manipulateKeyOn(key, pausingCache);
277 int i = 0;
278 while(!loaderHasReturned.get() && i < 20)
279 {
280
281
282 Thread.sleep(100L);
283 }
284 return null;
285 }
286 });
287
288 manipulationOp.get();
289
290 assertThat(loaderHasReturned.get(), is(true));
291 Long initialCacheValue = f.get(400, TimeUnit.MILLISECONDS);
292
293 Long cachedValue = pausingCache.get(key);
294 assertThat(initialCacheValue, is(1L));
295 assertThat(cachedValue, is(newCacheValue));
296 }
297 }