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.Ehcache;
20  import net.sf.ehcache.Element;
21  import net.sf.ehcache.StopWatch;
22  import net.sf.ehcache.CacheTest;
23  import net.sf.ehcache.Status;
24  import net.sf.ehcache.Cache;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import java.io.Serializable;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  
34  /**
35   * Test cases for the {@link BlockingCache}.
36   *
37   * @author Adam Murdoch
38   * @author Greg Luck
39   * @version $Id: BlockingCacheTest.java 512 2007-07-10 09:18:45Z gregluck $
40   */
41  public class BlockingCacheTest extends CacheTest {
42      private static final Log LOG = LogFactory.getLog(BlockingCacheTest.class.getName());
43      private BlockingCache blockingCache;
44  
45      /**
46       * Load up the test cache
47       */
48      protected void setUp() throws Exception {
49          super.setUp();
50          Ehcache cache = manager.getCache("sampleIdlingExpiringCache");
51          blockingCache = new BlockingCache(cache);
52      }
53  
54      /**
55       * teardown
56       */
57      protected void tearDown() throws Exception {
58          if (manager.getStatus() == Status.STATUS_ALIVE) {
59              blockingCache.removeAll();
60          }
61          super.tearDown();
62      }
63  
64      /**
65       * Tests adding and looking up an entry.
66       */
67      public void testAddEntry() throws Exception {
68          final String key = "key";
69          final String value = "value";
70          Element element = new Element(key, value);
71  
72          // Check the cache is empty
73          assertEquals(0, blockingCache.getKeys().size());
74  
75          // Put the entry
76          blockingCache.put(new Element(key, value));
77  
78          // Check there is a single entry
79          assertEquals(1, blockingCache.getKeys().size());
80          assertTrue(blockingCache.getKeys().contains(key));
81          final Element returnedElement = blockingCache.get(key);
82          assertEquals(element, returnedElement);
83  
84      }
85  
86      /**
87       * Tests that getting entries matches a list of known entries
88       */
89      public void testGetEntries() throws Exception {
90          Ehcache cache = blockingCache.getCache();
91          for (int i = 0; i < 100; i++) {
92              cache.put(new Element(new Integer(i), "value" + i));
93          }
94          List keys = blockingCache.getKeys();
95          List elements = new ArrayList();
96          for (int i = 0; i < keys.size(); i++) {
97              Object key = keys.get(i);
98              elements.add(blockingCache.get(key));
99          }
100         assertEquals(100, elements.size());
101         Map map = new HashMap();
102         for (int i = 0; i < elements.size(); i++) {
103             Element element = (Element) elements.get(i);
104             map.put(element.getObjectKey(), element.getObjectValue());
105         }
106         for (int i = 0; i < 100; i++) {
107             Serializable value = (Serializable) map.get(new Integer(i));
108             assertEquals("value" + i, value);
109         }
110     }
111 
112     /**
113      * Tests looking up a missing entry, then adding it.
114      */
115     public void testAddMissingEntry() throws Exception {
116         Element element = new Element("key", "value");
117 
118         // Make sure the entry does not exist
119         assertNull(blockingCache.get("key"));
120 
121         // Put the entry
122         blockingCache.put(element);
123 
124         // Check the entry is in the cache
125         assertEquals(1, blockingCache.getKeys().size());
126         assertEquals(element, blockingCache.get("key"));
127 
128     }
129 
130 
131     /**
132      * Does a second tread block until the first thread puts the entry?
133      */
134     public void testSecondThreadActuallyBlocks() throws Exception {
135         Element element = new Element("key", "value");
136         final List threadResults = new ArrayList();
137 
138         // Make sure the entry does not exist
139         assertNull(blockingCache.get("key"));
140 
141         Thread secondThread = new Thread() {
142                 public void run() {
143                     threadResults.add(blockingCache.get("key"));
144                 }
145             };
146         secondThread.start();
147         assertEquals(0, threadResults.size());
148 
149         // Put the entry
150         blockingCache.put(element);
151         Thread.sleep(30);
152         assertEquals(1, threadResults.size());
153         assertEquals(element, threadResults.get(0));
154 
155         // Check the entry is in the cache
156         assertEquals(1, blockingCache.getKeys().size());
157         assertEquals(element, blockingCache.get("key"));
158     }
159 
160     /**
161      * Elements with null valuea are not stored in the blocking cache
162      */
163     public void testUnknownEntry() throws Exception {
164         // Make sure the entry does not exist
165         assertNull(blockingCache.get("key"));
166         // Put the entry
167         blockingCache.put(new Element("key", null));
168         assertEquals(0, blockingCache.getKeys().size());
169     }
170 
171     /**
172      * Overwriting an Element with an element with a null value effectively removes it from the cache
173      */
174     public void testRemoveEntry() throws Exception {
175         Element element = new Element("key", "value");
176 
177         // Add entry and make sure it's there
178         blockingCache.put(element);
179         assertEquals(element, blockingCache.get("key"));
180 
181         // Remove the entry and make sure its gone
182         blockingCache.put(new Element("key", null));
183         assertEquals(0, blockingCache.getKeys().size());
184 
185     }
186 
187     /**
188      * Tests clearing the cache
189      */
190     public void testClear() throws Exception {
191         Ehcache cache = manager.getCache("sampleCacheNotEternalButNoIdleOrExpiry");
192         blockingCache = new BlockingCache(cache);
193         // Add some entries
194         blockingCache.put(new Element("key1", "value1"));
195         blockingCache.put(new Element("key2", "value2"));
196         blockingCache.put(new Element("key3", "value2"));
197         assertEquals(3, blockingCache.getKeys().size());
198 
199         // Clear the cache
200         blockingCache.removeAll();
201         assertEquals(0, blockingCache.getKeys().size());
202     }
203 
204 
205     /**
206      * Thrashes a BlockingCache and looks for liveness problems
207      * Note. These timings are without logging. Turn logging off to run this test.
208      */
209     public void testThrashBlockingCache() throws Exception {
210         Ehcache cache = manager.getCache("sampleCache1");
211         blockingCache = new BlockingCache(cache);
212         long duration = thrashCache(blockingCache, 50, 500L, 1000L);
213         LOG.debug("Thrash Duration:" + duration);
214     }
215 
216     /**
217      * Thrashes a BlockingCache which has a tiny timeout. Should throw
218      * a LockTimeoutException caused by queued threads not getting the lock
219      * in the required time.
220      */
221     public void testThrashBlockingCacheTinyTimeout() throws Exception {
222         Ehcache cache = manager.getCache("sampleCache1");
223         blockingCache = new BlockingCache(cache);
224         blockingCache.setTimeoutMillis(1);
225         long duration = 0;
226         try {
227             duration = thrashCache(blockingCache, 50, 400L, 1000L);
228             fail();
229         } catch (Exception e) {
230             assertEquals(LockTimeoutException.class, e.getCause().getClass());
231         }
232         LOG.debug("Thrash Duration:" + duration);
233     }
234 
235     /**
236      * Thrashes a BlockingCache which has a reasonable timeout. Should work.
237      * The old implementation, which had scalability limits, needed 5, 1000L, 5000L to pass
238      */
239     public void testThrashBlockingCacheReasonableTimeout() throws Exception {
240         Ehcache cache = manager.getCache("sampleCache1");
241         blockingCache = new BlockingCache(cache);
242         blockingCache.setTimeoutMillis((int) (400 * StopWatch.getSpeedAdjustmentFactor()));
243         long duration = thrashCache(blockingCache, 50, 400L, (long) (1000L * StopWatch.getSpeedAdjustmentFactor()));
244         LOG.debug("Thrash Duration:" + duration);
245     }
246 
247         /**
248          * This method tries to get the cache to slow up.
249          * It creates 300 threads, does blocking gets and monitors the liveness right the way through
250          */
251         private long thrashCache(final BlockingCache cache, final int numberOfThreads,
252                                  final long liveness, final long retrievalTime) throws Exception {
253             StopWatch stopWatch = new StopWatch();
254 
255             // Create threads that do gets
256             final List executables = new ArrayList();
257             for (int i = 0; i < numberOfThreads; i++) {
258                 final Executable executable = new Executable() {
259                     public void execute() throws Exception {
260                         for (int i = 0; i < 10; i++) {
261                             final String key = "key" + i;
262                             Object value = cache.get(key);
263                             checkLiveness(cache, liveness);
264                             if (value == null) {
265                                 cache.put(new Element(key, "value" + i));
266                             }
267                             //The key will be in. Now check we can get it quickly
268                             checkRetrievalOnKnownKey(cache, retrievalTime, key);
269                         }
270                     }
271                 };
272                 executables.add(executable);
273             }
274 
275             runThreads(executables);
276             cache.removeAll();
277             return stopWatch.getElapsedTime();
278         }
279 
280     /**
281      * Checks that the liveness method returns in less than a given amount of time.
282      * liveness() is a method that simply returns a String. It should be very fast. It can be
283      * delayed because it is a synchronized method, and must acquire an object lock before continuing
284      * The old blocking cache was taking up to several minutes in production
285      *
286      * @param cache a BlockingCache
287      */
288     private void checkLiveness(BlockingCache cache, long liveness) {
289         StopWatch stopWatch = new StopWatch();
290         cache.liveness();
291         long measuredLiveness = stopWatch.getElapsedTime();
292         assertTrue("liveness is " + measuredLiveness + " but should be less than " + liveness + "ms",
293                 measuredLiveness < liveness);
294     }
295 
296     /**
297      * Checks that the liveness method returns in less than a given amount of time.
298      * liveness() is a method that simply returns a String. It should be very fast. It can be
299      * delayed because it is a synchronized method, and must acquire
300      * an object lock before continuing. The old blocking cache was taking up to several minutes in production
301      *
302      * @param cache a BlockingCache
303      */
304     private void checkRetrievalOnKnownKey(BlockingCache cache, long requiredRetrievalTime, Serializable key)
305             throws LockTimeoutException {
306         StopWatch stopWatch = new StopWatch();
307         cache.get(key);
308         long measuredRetrievalTime = stopWatch.getElapsedTime();
309         assertTrue("Retrieval time on known key is " + measuredRetrievalTime
310                 + " but should be less than " + requiredRetrievalTime + "ms",
311                 measuredRetrievalTime < requiredRetrievalTime);
312     }
313 
314     /**
315      * Creates a blocking test cache
316      */
317     protected Ehcache createTestCache() {
318         Ehcache cache = super.createTestCache();
319         return new BlockingCache(cache);
320 }
321 
322     /**
323      * Gets the sample cache 1
324      */
325     protected Ehcache getSampleCache1() {
326         Cache cache = manager.getCache("sampleCache1");
327         manager.replaceCacheWithDecoratedCache(cache, new BlockingCache(cache));
328         return manager.getEhcache("sampleCache1");
329     }
330 
331     /**
332      * Use to manually test super class tests
333      */
334     public void testInstrumented() throws Exception {
335         super.testSizes();
336     }
337 }
338