View Javadoc

1   /***
2    *  Copyright 2003-2010 Terracotta, Inc.
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;
18  
19  import junit.framework.Assert;
20  import net.sf.ehcache.store.LruMemoryStoreTest;
21  import net.sf.ehcache.store.MemoryStore;
22  import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
23  import net.sf.ehcache.store.Store;
24  import org.junit.After;
25  import org.junit.Before;
26  import org.junit.Ignore;
27  import org.junit.Test;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import java.io.IOException;
32  import java.util.ArrayList;
33  import java.util.Date;
34  import java.util.List;
35  import java.util.Random;
36  
37  import static junit.framework.Assert.assertTrue;
38  import static org.junit.Assert.*;
39  
40  /***
41   * Other than policy differences, the Store implementations should work identically
42   *
43   * @author Greg Luck
44   * @version $Id: MemoryStoreTester.html 13146 2011-08-01 17:12:39Z oletizi $
45   */
46  @Ignore
47  public class MemoryStoreTester extends AbstractCacheTest {
48  
49      private static final Logger LOG = LoggerFactory.getLogger(MemoryStoreTester.class.getName());
50  
51      /***
52       * The memory store that tests will be performed on
53       */
54      protected Store store;
55  
56  
57      /***
58       * The cache under test
59       */
60      protected Cache cache;
61  
62  
63      /***
64       * For automatic suite generators
65       */
66      @Test
67      public void testNoop() {
68          //noop
69      }
70  
71      /***
72       * setup test
73       */
74      @Override
75      @Before
76      public void setUp() throws Exception {
77          manager = CacheManager.getInstance();
78      }
79  
80      /***
81       * teardown
82       */
83      @Override
84      @After
85      public void tearDown() throws Exception {
86          try {
87              if (cache != null) {
88                  cache.removeAll();
89                  cache = null;
90              }
91              if (manager != null) {
92                  manager.shutdown();
93                  manager = null;
94              }
95          } catch (OutOfMemoryError e) {
96              //OutOfMemoryError Happens at different places on Apache LRU for some reason
97              LOG.info(e.getMessage());
98          } catch (Throwable t) {
99              //OutOfMemoryError Happens at different places on Apache LRU for some reason
100             LOG.info(t.getMessage());
101         }
102     }
103 
104     /***
105      * Creates a cache with the given policy and adds it to the manager.
106      *
107      * @param evictionPolicy
108      * @throws CacheException
109      */
110     protected void createMemoryOnlyStore(MemoryStoreEvictionPolicy evictionPolicy) throws CacheException {
111         manager.removeCache("testMemoryOnly");
112         cache = new Cache("testMemoryOnly", 12000, evictionPolicy, false, System.getProperty("java.io.tmpdir"),
113                 false, 60, 30, false, 60, null);
114         manager.addCache(cache);
115         store = cache.getStore();
116     }
117 
118     /***
119      * Creates a cache with the given policy with a MemoryStore only and adds it to the manager.
120      *
121      * @param evictionPolicy
122      * @throws CacheException
123      */
124     protected void createMemoryOnlyStore(MemoryStoreEvictionPolicy evictionPolicy, int memoryStoreSize) throws CacheException {
125         manager.removeCache("test");
126         cache = new Cache("test", memoryStoreSize, evictionPolicy, false, null, false, 60, 30, false, 60, null);
127         manager.addCache(cache);
128         store = cache.getStore();
129     }
130 
131     /***
132      * Creates a store from the given configuration and cache within it.
133      *
134      * @param filePath
135      * @param cacheName
136      * @throws CacheException
137      */
138     protected void createMemoryStore(String filePath, String cacheName) throws CacheException {
139         manager.shutdown();
140         manager = CacheManager.create(filePath);
141         cache = manager.getCache(cacheName);
142         store = cache.getStore();
143     }
144 
145 
146     /***
147      * Test elements can be put in the store
148      */
149     protected void putTest() throws IOException {
150         Element element;
151 
152         assertEquals(0, store.getSize());
153 
154         element = new Element("key1", "value1");
155         store.put(element);
156         assertEquals(1, store.getSize());
157         assertEquals("value1", store.get("key1").getObjectValue());
158 
159         element = new Element("key2", "value2");
160         store.put(element);
161         assertEquals(2, store.getSize());
162         assertEquals("value2", store.get("key2").getObjectValue());
163 
164         for (int i = 0; i < 1999; i++) {
165             store.put(new Element("" + i, new Date()));
166         }
167 
168         assertEquals(4, store.getSize());
169         assertEquals(2001, cache.getSize());
170         assertEquals(3998, cache.getDiskStoreSize());
171 
172         /***
173          * Non serializable test class
174          */
175         class NonSerializable {
176             //
177         }
178 
179         store.put(new Element(new NonSerializable(), new NonSerializable()));
180 
181         assertEquals(4, store.getSize());
182         assertEquals(2002, cache.getSize());
183         assertEquals(1999, cache.getDiskStoreSize());
184 //        assertEquals(1998, cache.getDiskStoreSize());    ???
185 
186         //smoke test
187         for (int i = 0; i < 2000; i++) {
188             store.get("" + i);
189         }
190     }
191 
192     /***
193      * Test elements can be removed from the store
194      */
195     protected void removeTest() throws IOException {
196         Element element;
197 
198         element = new Element("key1", "value1");
199         store.put(element);
200         assertEquals(1, store.getSize());
201 
202         store.remove("key1");
203         assertEquals(0, store.getSize());
204 
205         store.put(new Element("key2", "value2"));
206         store.put(new Element("key3", "value3"));
207         assertEquals(2, store.getSize());
208 
209         assertNotNull(store.remove("key2"));
210         assertEquals(1, store.getSize());
211 
212         // Try to remove an object that is not there in the store
213         assertNull(store.remove("key4"));
214         assertEquals(1, store.getSize());
215 
216         //check no NPE on key handling
217         assertNull(store.remove(null));
218 
219     }
220 
221 
222     /***
223      * Check no NPE on put
224      */
225     @Test
226     public void testNullPut() throws IOException {
227         store.put(null);
228     }
229 
230     /***
231      * Check no NPE on get
232      */
233     @Test
234     public void testNullGet() throws IOException {
235         assertNull(store.get(null));
236     }
237 
238     /***
239      * Check no NPE on remove
240      */
241     @Test
242     public void testNullRemove() throws IOException {
243         assertNull(store.remove(null));
244     }
245 
246     /***
247      * Tests looking up an entry that does not exist.
248      */
249     @Test
250     public void testGetUnknown() throws Exception {
251         final Element element = store.get("key");
252         assertNull(element);
253     }
254 
255     /***
256      * Tests adding an entry.
257      */
258     @Test
259     public void testPut() throws Exception {
260         final String value = "value";
261         final String key = "key";
262 
263         // Make sure the element is not found
264         assertEquals(0, store.getSize());
265         Element element = store.get(key);
266         assertNull(element);
267 
268         // Add the element
269         element = new Element(key, value);
270         store.put(element);
271 
272         // Get the element
273         assertEquals(1, store.getSize());
274         element = store.get(key);
275         assertNotNull(element);
276         assertEquals(value, element.getObjectValue());
277     }
278 
279     /***
280      * Tests removing an entry.
281      */
282     @Test
283     public void testRemove() throws Exception {
284         final String value = "value";
285         final String key = "key";
286 
287         // Add the entry
288 
289         Element element = new Element(key, value);
290         store.put(element);
291 
292         // Check the entry is there
293         assertEquals(1, store.getSize());
294         element = store.get(key);
295         assertNotNull(element);
296 
297         // Remove it
298         store.remove(key);
299 
300         // Check the entry is not there
301         assertEquals(0, store.getSize());
302         element = store.get(key);
303         assertNull(element);
304     }
305 
306     /***
307      * Tests removing all the entries.
308      */
309     @Test
310     public void testRemoveAll() throws Exception {
311         final String value = "value";
312         final String key = "key";
313 
314         // Add the entry
315         Element element = new Element(key, value);
316         store.put(element);
317 
318         // Check the entry is there
319         element = store.get(key);
320         assertNotNull(element);
321 
322         // Remove it
323         store.removeAll();
324 
325         // Check the entry is not there
326         assertEquals(0, store.getSize());
327         element = store.get(key);
328         assertNull(element);
329     }
330 
331     /***
332      * Multi-thread read-only test.
333      */
334     @Test
335     public void testReadOnlyThreads() throws Exception {
336 
337         // Add a couple of elements
338         store.put(new Element("key0", "value"));
339         store.put(new Element("key1", "value"));
340 
341         // Run a set of threads, that attempt to fetch the elements
342         final List executables = new ArrayList();
343         for (int i = 0; i < 10; i++) {
344             final String key = "key" + (i % 2);
345             final MemoryStoreTester.Executable executable = new LruMemoryStoreTest.Executable() {
346                 public void execute() throws Exception {
347                     final Element element = store.get(key);
348                     assertNotNull(element);
349                     assertEquals("value", element.getObjectValue());
350                 }
351             };
352             executables.add(executable);
353         }
354         runThreads(executables);
355     }
356 
357     /***
358      * Multi-thread read-write test.
359      */
360     @Test
361     public void testReadWriteThreads() throws Exception {
362 
363         final String value = "value";
364         final String key = "key";
365 
366         // Add the entry
367         final Element element = new Element(key, value);
368         store.put(element);
369 
370         // Run a set of threads that get, put and remove an entry
371         final List executables = new ArrayList();
372         for (int i = 0; i < 5; i++) {
373             final MemoryStoreTester.Executable executable = new MemoryStoreTester.Executable() {
374                 public void execute() throws Exception {
375                     final Element element = store.get("key");
376                     assertNotNull(element);
377                 }
378             };
379             executables.add(executable);
380         }
381         for (int i = 0; i < 5; i++) {
382             final MemoryStoreTester.Executable executable = new MemoryStoreTester.Executable() {
383                 public void execute() throws Exception {
384                     store.put(element);
385                 }
386             };
387             executables.add(executable);
388         }
389 
390         runThreads(executables);
391     }
392 
393     /***
394      * Multi-thread read, put and removeAll test.
395      * This checks for memory leaks
396      * using the removeAll which was the known cause of memory leaks with MemoryStore in JCS
397      */
398     @Test
399     public void testMemoryLeak() throws Exception {
400         long differenceMemoryCache = thrashCache();
401         LOG.info("Difference is : " + differenceMemoryCache);
402         //Sometimes this can be higher but a three hour run confirms no memory leak. Consider increasing.
403         assertTrue("Memory difference was expected to be less than 500000, but was " + differenceMemoryCache, differenceMemoryCache < 500000);
404     }
405 
406 
407     /***
408      * This method tries to get the store too leak.
409      */
410     protected long thrashCache() throws Exception {
411 
412 
413         long startingSize = measureMemoryUse();
414         LOG.info("Starting memory used is: " + startingSize);
415 
416         final String value = "value";
417         final String key = "key";
418 
419         // Add the entry
420         final Element element = new Element(key, value);
421         store.put(element);
422 
423         // Create 15 threads that read the keys;
424         final List executables = new ArrayList();
425         for (int i = 0; i < 15; i++) {
426             final LruMemoryStoreTest.Executable executable = new MemoryStoreTester.Executable() {
427                 public void execute() throws Exception {
428                     for (int i = 0; i < 500; i++) {
429                         final String key = "key" + i;
430                         store.get(key);
431                     }
432                     store.get("key");
433                 }
434             };
435             executables.add(executable);
436         }
437         //Create 15 threads that are insert 500 keys with large byte[] as values
438         for (int i = 0; i < 15; i++) {
439             final MemoryStoreTester.Executable executable = new MemoryStoreTester.Executable() {
440                 public void execute() throws Exception {
441 
442                     // Add a bunch of entries
443                     for (int i = 0; i < 500; i++) {
444                         // Use a random length value
445                         final String key = "key" + i;
446                         byte[] value = new byte[10000];
447                         Element element = new Element(key, value);
448                         store.put(element);
449                     }
450                 }
451             };
452             executables.add(executable);
453         }
454 
455         runThreads(executables);
456         store.removeAll();
457 
458         long finishingSize = measureMemoryUse();
459         LOG.info("Ending memory used is: " + finishingSize);
460         return finishingSize - startingSize;
461     }
462 
463 
464     /***
465      * Multi-thread read-write test.
466      */
467     @Test
468     public void testReadWriteThreadsSurya() throws Exception {
469 
470         long start = System.currentTimeMillis();
471         final List executables = new ArrayList();
472         final Random random = new Random();
473 
474         // 50% of the time get data
475         for (int i = 0; i < 10; i++) {
476             final Executable executable = new Executable() {
477                 public void execute() throws Exception {
478                     store.get("key" + random.nextInt(10000));
479                 }
480             };
481             executables.add(executable);
482         }
483 
484         //25% of the time add data
485         for (int i = 0; i < 5; i++) {
486             final Executable executable = new Executable() {
487                 public void execute() throws Exception {
488                     store.put(new Element("key" + random.nextInt(20000), "value"));
489                 }
490             };
491             executables.add(executable);
492         }
493 
494         //25% if the time remove the data
495         for (int i = 0; i < 5; i++) {
496             final Executable executable = new Executable() {
497                 public void execute() throws Exception {
498                     store.remove("key" + random.nextInt(10000));
499                 }
500             };
501             executables.add(executable);
502         }
503 
504         runThreads(executables);
505         long end = System.currentTimeMillis();
506         LOG.info("Total time for the test: " + (end + start) + " ms");
507     }
508 
509 
510     /***
511      * Test behaviour of memory store using 1 million records.
512      * This is expected to run out of memory on a 64MB machine. Where it runs out
513      * is asserted so that design changes do not start using more memory per element.
514      * <p/>
515      * This test will fail (ie not get an out of memory error) on VMs configured to be server which do not have a fixed upper memory limit.
516      * <p/>
517      * Takes too long to run therefore switch off
518      * <p/>
519      * These memory size asserts were 100,000 and 60,000. The ApacheLRU map does not get quite as high numbers.
520      * This test varies according to architecture. 64 bit architectures
521      */
522     @Test
523     public void testMemoryStoreOutOfMemoryLimit() throws Exception {
524         LOG.info("Starting out of memory limit test");
525         //Set size so the second element overflows to disk.
526         cache = manager.getCache("memoryLimitTest");
527         if (cache == null) {
528             cache = new Cache("memoryLimitTest", 1000000, false, false, 500, 500);
529             manager.addCache(cache);
530         }
531         int i = 0;
532         try {
533             for (; i < 1000000; i++) {
534                 cache.put(new Element("" +
535                         i, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
536                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
537                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
538                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
539                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
540                         + "AAAAA " + i));
541             }
542             LOG.info("About to fail out of memory limit test");
543             fail();
544         } catch (OutOfMemoryError e) {
545             cache.removeAll();
546             Thread.sleep(1000);
547             System.gc();
548             Thread.sleep(2000);
549             System.gc();
550 
551             try {
552                 LOG.info("Ran out of memory putting " + i + "th element");
553                 assertTrue(i > 65000);
554             } catch (OutOfMemoryError e1) {
555                 //sometimes if we are really out of memory we cannot do anything
556             }
557 
558         }
559         Thread.sleep(1000);
560         System.gc();
561         Thread.sleep(1000);
562     }
563 
564     @Test
565     public void testShrinkingAndGrowingMemoryStore() {
566         cache = new Cache("testShrinkingAndGrowingMemoryStore", 50, false, false, 120, 120);
567         manager.addCache(cache);
568         store = cache.getStore();
569 
570         if (!(store instanceof MemoryStore)) {
571             LOG.info("Skipping Growing/Shrinking Memory Store Test - Store is not a subclass of MemoryStore!");
572             return;
573         }
574 
575         int i = 0;
576         for (; ;) {
577             int size = store.getSize();
578             store.put(new Element(Integer.valueOf(i++), new byte[100]));
579             if (store.getSize() <= size) break;
580         }
581 
582         final int initialSize = store.getSize();
583         final int shrinkSize = initialSize / 2;
584         ((MemoryStore) store).memoryCapacityChanged(initialSize, shrinkSize);
585 
586         for (; ;) {
587             int size = store.getSize();
588             store.put(new Element(Integer.valueOf(i++), new byte[100]));
589             if (store.getSize() >= size) break;
590         }
591 
592         {
593             int size = store.getSize();
594             assertTrue(size < (shrinkSize * 1.1));
595             assertTrue(size > (shrinkSize * 0.9));
596         }
597 
598         final int growSize = initialSize * 2;
599         ((MemoryStore) store).memoryCapacityChanged(shrinkSize, growSize);
600 
601         for (; ;) {
602             int size = store.getSize();
603             store.put(new Element(Integer.valueOf(i++), new byte[100]));
604             if (store.getSize() <= size) break;
605         }
606 
607         {
608             int size = store.getSize();
609             assertTrue(size < (growSize * 1.1));
610             assertTrue(size > (growSize * 0.9));
611         }
612     }
613 
614     @Test
615     public void testElementPinning() throws Exception {
616         createMemoryOnlyStore(MemoryStoreEvictionPolicy.LRU, 20);
617 
618         for (int i = 0; i < 200; i++) {
619             Element element = new Element("Ku-" + i, "@" + i);
620             store.put(element);
621         }
622 
623         Assert.assertEquals(20, store.getSize());
624 
625         for (int i = 0; i < 200; i++) {
626             Element element = new Element("Kp-" + i, "#" + i);
627             element.setTimeToIdle(1);
628             element.setTimeToLive(1);
629             element.setPinned(true);
630             store.put(element);
631         }
632 
633         for (int i = 0; i < 200; i++) {
634             assertTrue("missing key Kp-" + i, store.containsKey("Kp-" + i));
635         }
636 
637         // wait until all pinned elements expire
638         Thread.sleep(1100);
639 
640         for (int i = 1000; i < 1200; i++) {
641             Element element = new Element("Ku-" + i, "#" + i);
642             store.put(element);
643         }
644 
645         Assert.assertEquals(20, store.getSize());
646     }
647 
648 }