1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.constructs.blocking;
18  
19  import net.sf.ehcache.Cache;
20  import net.sf.ehcache.CacheManager;
21  import net.sf.ehcache.CacheTest;
22  import net.sf.ehcache.Ehcache;
23  import net.sf.ehcache.Element;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  /**
28   * Test cases for the {@link SelfPopulatingCache}.
29   *
30   * @author Adam Murdoch
31   * @author Greg Luck
32   * @version $Id: SelfPopulatingCacheTest.java 512 2007-07-10 09:18:45Z gregluck $
33   */
34  public class SelfPopulatingCacheTest extends CacheTest {
35      private static final Log LOG = LogFactory.getLog(SelfPopulatingCache.class.getName());
36  
37      /**
38       * Shared with subclass
39       */
40      protected CacheManager manager;
41      /**
42       * Shared with subclass
43       */
44      protected SelfPopulatingCache selfPopulatingCache;
45      /**
46       * Shared with subclass
47       */
48      protected Ehcache cache;
49  
50      /**
51       * Number of factory requests
52       */
53      protected volatile int cacheEntryFactoryRequests;
54  
55      /**
56       * Load up the test cache
57       */
58      protected void setUp() throws Exception {
59          super.setUp();
60          manager = new CacheManager();
61          cache = manager.getCache("sampleIdlingExpiringCache");
62          selfPopulatingCache = new SelfPopulatingCache(cache, new CountingCacheEntryFactory("value"));
63          cacheEntryFactoryRequests = 0;
64      }
65  
66      /**
67       * teardown
68       */
69      protected void tearDown() throws Exception {
70          selfPopulatingCache.removeAll();
71          manager.shutdown();
72          super.tearDown();
73      }
74  
75      /**
76       * Tests fetching an entry.
77       */
78      public void testFetch() throws Exception {
79  
80          // Lookup
81          final Element element = selfPopulatingCache.get("key");
82          assertEquals("value", element.getValue());
83      }
84  
85      /**
86       * Tests fetching an unknown entry.
87       */
88      public void testFetchUnknown() throws Exception {
89          final CacheEntryFactory factory = new CountingCacheEntryFactory(null);
90          selfPopulatingCache = new SelfPopulatingCache(cache, factory);
91  
92          // Lookup
93          assertNull(cache.get("key"));
94      }
95  
96      /**
97       * Tests when fetch fails.
98       */
99      public void testFetchFail() throws Exception {
100         final Exception exception = new Exception("Failed.");
101         final CacheEntryFactory factory = new CacheEntryFactory() {
102             public Object createEntry(final Object key) throws Exception {
103                 throw exception;
104             }
105         };
106         selfPopulatingCache = new SelfPopulatingCache(cache, factory);
107 
108         // Lookup
109         try {
110             selfPopulatingCache.get("key");
111             fail();
112         } catch (final Exception e) {
113             Thread.sleep(20);
114             // Check the error
115             assertEquals("Could not fetch object for cache entry \"key\".", e.getMessage());
116         }
117     }
118 
119     /**
120      * Tests that an entry is created once only.
121      */
122     public void testCreateOnce() throws Exception {
123         final String value = "value";
124         final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value);
125         selfPopulatingCache = new SelfPopulatingCache(cache, factory);
126 
127         // Fetch the value several times
128         for (int i = 0; i < 5; i++) {
129             assertSame(value, selfPopulatingCache.get("key").getObjectValue());
130             assertEquals(1, factory.getCount());
131         }
132     }
133 
134     /**
135      * Tests refreshing the entries.
136      */
137     public void testRefresh() throws Exception {
138         final String value = "value";
139         final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value);
140         selfPopulatingCache = new SelfPopulatingCache(cache, factory);
141 
142         // Check the value
143         assertSame(value, selfPopulatingCache.get("key").getObjectValue());
144         assertEquals(1, factory.getCount());
145 
146         // Refresh
147         selfPopulatingCache.refresh();
148         assertEquals(2, factory.getCount());
149 
150         // Check the value
151         assertSame(value, selfPopulatingCache.get("key").getObjectValue());
152         assertEquals(2, factory.getCount());
153 
154     }
155 
156     /**
157      * Tests that the current thread, which gets renamed when it enters a SelfPopulatingCache, comes out with
158      * its old name.
159      */
160     public void testThreadNaming() throws Exception {
161         final String value = "value";
162         final CountingCacheEntryFactory factory = new CountingCacheEntryFactory(value);
163         selfPopulatingCache = new SelfPopulatingCache(cache, factory);
164 
165         String originalThreadName = Thread.currentThread().getName();
166 
167         // Check the value
168         selfPopulatingCache.get("key");
169         assertEquals(originalThreadName, Thread.currentThread().getName());
170 
171         // Refresh
172         selfPopulatingCache.refresh();
173         assertEquals(originalThreadName, Thread.currentThread().getName());
174 
175         // Check the value with null key
176         selfPopulatingCache.get(null);
177         assertEquals(originalThreadName, Thread.currentThread().getName());
178 
179 
180     }
181 
182     /**
183      * Tests discarding little used entries.
184      * <cache name="sampleIdlingExpiringCache"
185      * maxElementsInMemory="1"
186      * eternal="false"
187      * timeToIdleSeconds="2"
188      * timeToLiveSeconds="5"
189      * overflowToDisk="true"
190      * />
191      */
192     public void testDiscardLittleUsed() throws Exception {
193         final CacheEntryFactory factory = new CountingCacheEntryFactory("value");
194         selfPopulatingCache = new SelfPopulatingCache(cache, factory);
195 
196 
197         selfPopulatingCache.get("key1");
198         selfPopulatingCache.get("key2");
199         assertEquals(2, selfPopulatingCache.getSize());
200         selfPopulatingCache.refresh();
201         assertEquals(2, selfPopulatingCache.getSize());
202         Thread.sleep(2020);
203 
204         //Will be two, because counting expired elements
205         assertEquals(2, selfPopulatingCache.getSize());
206 
207         // Check the cache
208         selfPopulatingCache.removeAll();
209         assertEquals(0, selfPopulatingCache.getSize());
210     }
211 
212     /**
213      * Tests discarding little used entries, where refreshing is slow.
214      * <cache name="sampleIdlingExpiringCache"
215      * maxElementsInMemory="1"
216      * eternal="false"
217      * timeToIdleSeconds="2"
218      * timeToLiveSeconds="5"
219      * overflowToDisk="true"
220      * />
221      */
222     public void testDiscardLittleUsedSlow() throws Exception {
223         final CacheEntryFactory factory = new CacheEntryFactory() {
224             public Object createEntry(final Object key) throws Exception {
225                 Thread.sleep(200);
226                 return key;
227             }
228         };
229         selfPopulatingCache = new SelfPopulatingCache(cache, factory);
230     }
231 
232 
233     /**
234      * Expected: If multiple threads try to retrieve the same key from a
235      * SelfPopulatingCache at the same time, and that key is not yet in the cache,
236      * one thread obtains the lock for that key and uses the CacheEntryFactory to
237      * generate the cache entry and all other threads wait on the lock.
238      * Any and all threads which timeout while waiting for this lock should fail
239      * to acquire the lock for that key and throw an exception.
240      * <p/>
241      * This thread tests for this by having several threads to a cache "get" for
242      * the same key, allowing one to acquire the lock and the others to wait.  The
243      * one that acquires the lock and attempts to generate the cache entry for the
244      * key waits for a period of time long enough to allow all other threads to
245      * timeout waiting for the lock.  Any thread that succeeds in acquiring the lock,
246      * including the first to do so, increment a counter when they begin creating
247      * the cache entry using the CacheEntryFactory.  It is expected that this
248      * counter will only be "1" after all threads complete since all but the
249      * first to acquire it should timeout and throw exceptions.
250      * <p/>
251      * We then test that a thread that comes along later increments the counter.
252      */
253     public void testSelfPopulatingBlocksWithTimeoutSetNull() throws InterruptedException {
254         selfPopulatingCache = new SelfPopulatingCache(new Cache("TestCache", 50, false, false, 0, 0), new NullCachePopulator());
255         selfPopulatingCache.setTimeoutMillis(200);
256         manager.addCache(selfPopulatingCache);
257 
258         CacheAccessorThread[] cacheAccessorThreads = new CacheAccessorThread[10];
259 
260         for (int i = 0; i < cacheAccessorThreads.length; i++) {
261             cacheAccessorThreads[i] = new CacheAccessorThread(selfPopulatingCache, "key1");
262             cacheAccessorThreads[i].start();
263             // Do a slight delay here so that all the timeouts
264             // don't happen simultaneously - this is key
265             try {
266                 Thread.sleep(20);
267             } catch (InterruptedException ignored) {
268                 //
269             }
270         }
271 
272         //All of the others should have timed out. The first thread will have returned null.
273         // This thread should be able to have a go, thus setting the count to 2
274         Thread.sleep(1000);
275         Thread lateThread = new CacheAccessorThread(selfPopulatingCache, "key1");
276         lateThread.start();
277         lateThread.join();
278 
279         assertEquals("Too many cacheAccessorThreads tried to create selfPopulatingCache entry for key1",
280                 2, cacheEntryFactoryRequests);
281     }
282 
283 
284     /**
285      * Creating 11 Threads which attempt to get a null entry will result, eventually, in 11
286      * calls to the CacheEntryFactory
287      * @throws InterruptedException
288      */
289     public void testSelfPopulatingBlocksWithoutTimeoutSetNull() throws InterruptedException {
290         selfPopulatingCache = new SelfPopulatingCache(new Cache("TestCache", 50, false, false, 0, 0), new NullCachePopulator());
291         //selfPopulatingCache.setTimeoutMillis(200);
292         manager.addCache(selfPopulatingCache);
293 
294         CacheAccessorThread[] cacheAccessorThreads = new CacheAccessorThread[10];
295 
296         for (int i = 0; i < cacheAccessorThreads.length; i++) {
297             cacheAccessorThreads[i] = new CacheAccessorThread(selfPopulatingCache, "key1");
298             cacheAccessorThreads[i].start();
299             // Do a slight delay here so that all the timeouts
300             // don't happen simultaneously - this is key
301             try {
302                 Thread.sleep(20);
303             } catch (InterruptedException ignored) {
304                 //
305             }
306         }
307 
308         //All of the others should have timed out. The first thread will have returned null.
309         // This thread should be able to have a go, thus setting the count to 2
310         Thread.sleep(12000);
311         Thread lateThread = new CacheAccessorThread(selfPopulatingCache, "key1");
312         lateThread.start();
313         lateThread.join();
314 
315         assertEquals("The wrong number of cacheAccessorThreads tried to create selfPopulatingCache entry for key1",
316                 11, cacheEntryFactoryRequests);
317     }
318 
319     /**
320      * Creating 11 Threads which attempt to get a non-null entry will result in 1
321      * call to the CacheEntryFactory
322      * @throws InterruptedException
323      */
324     public void testSelfPopulatingBlocksWithoutTimeoutSetNonNull() throws InterruptedException {
325         selfPopulatingCache = new SelfPopulatingCache(new Cache("TestCache", 50, false, false, 0, 0),
326                 new NonNullCachePopulator());
327         //selfPopulatingCache.setTimeoutMillis(200);
328         manager.addCache(selfPopulatingCache);
329 
330         CacheAccessorThread[] cacheAccessorThreads = new CacheAccessorThread[10];
331 
332         for (int i = 0; i < cacheAccessorThreads.length; i++) {
333             cacheAccessorThreads[i] = new CacheAccessorThread(selfPopulatingCache, "key1");
334             cacheAccessorThreads[i].start();
335             // Do a slight delay here so that all the timeouts
336             // don't happen simultaneously - this is key
337             try {
338                 Thread.sleep(20);
339             } catch (InterruptedException ignored) {
340                 //
341             }
342         }
343 
344         //All of the others should have timed out. The first thread will have returned null.
345         // This thread should be able to have a go, thus setting the count to 2
346         Thread.sleep(2000);
347         Thread lateThread = new CacheAccessorThread(selfPopulatingCache, "key1");
348         lateThread.start();
349         lateThread.join();
350 
351         assertEquals("The wrong number of cacheAccessorThreads tried to create selfPopulatingCache entry for key1",
352                 1, cacheEntryFactoryRequests);
353     }
354 
355 
356     /**
357      * A thread that accesses a selfpopulating cache
358      */
359     private final class CacheAccessorThread extends Thread {
360         private Ehcache cache;
361         private String key;
362 
363         private CacheAccessorThread(Ehcache cache, String key) {
364             this.cache = cache;
365             this.key = key;
366         }
367 
368         /**
369          * Thread run method
370          */
371         public void run() {
372             try {
373                 cache.get(key);
374             } catch (Exception e) {
375                 LOG.info("Exception: " + e.getMessage());
376             }
377         }
378     }
379 
380     /**
381      * A cache entry factory that sleeps beyond the lock timeout
382      */
383     private class NullCachePopulator implements CacheEntryFactory {
384 
385         public Object createEntry(Object key) throws Exception {
386             cacheEntryFactoryRequests++;
387             Thread.sleep(1000);
388             return null;
389         }
390     }
391 
392 
393     /**
394      * A cache entry factory that sleeps beyond the lock timeout
395      */
396     private class NonNullCachePopulator implements CacheEntryFactory {
397 
398         public Object createEntry(Object key) throws Exception {
399             cacheEntryFactoryRequests++;
400             Thread.sleep(1000);
401             return "value";
402         }
403     }
404 }
405 
406