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;
18  
19  import net.sf.ehcache.store.DiskStore;
20  import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
21  import net.sf.ehcache.store.Primitive;
22  import net.sf.ehcache.config.DiskStoreConfiguration;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import java.io.IOException;
27  import java.io.File;
28  import java.io.RandomAccessFile;
29  import java.io.FileNotFoundException;
30  import java.io.FileOutputStream;
31  import java.io.ByteArrayOutputStream;
32  import java.io.ObjectOutputStream;
33  import java.util.Random;
34  import java.util.List;
35  import java.util.ArrayList;
36  import java.util.Date;
37  
38  /**
39   * Test cases for the DiskStore.
40   *
41   * @author <a href="mailto:amurdoch@thoughtworks.com">Adam Murdoch</a>
42   * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
43   * @version $Id: DiskStoreTest.java 512 2007-07-10 09:18:45Z gregluck $
44   *          <p/>
45   *          total time 149 old i/o
46   *          total time 133, 131, 130 nio
47   */
48  public class DiskStoreTest extends AbstractCacheTest {
49      private static final Log LOG = LogFactory.getLog(DiskStoreTest.class.getName());
50      private static final int ELEMENT_ON_DISK_SIZE = 1340;
51      private CacheManager manager2;
52  
53      /**
54       * teardown
55       */
56      protected void tearDown() throws Exception {
57          super.tearDown();
58          if (manager2 != null) {
59              manager2.shutdown();
60          }
61          deleteFile("persistentLongExpiryIntervalCache");
62          deleteFile("fileTest");
63          deleteFile("testPersistent");
64      }
65  
66      /**
67       * Creates a store which is non-expiring so that we can check for
68       * size-related characteristics without elements being deleted under us.
69       */
70      private DiskStore createNonExpiringDiskStore() {
71          Cache cache = new Cache("testNonPersistent", 10000, true, true, 2, 1, false, 1);
72          manager.addCache(cache);
73          DiskStore diskStore = (DiskStore)cache.getDiskStore();
74          return diskStore;
75      }
76  
77      private DiskStore createDiskStore() {
78          Cache cache = new Cache("testNonPersistent", 10000, true, false, 2, 1, false, 1);
79          manager.addCache(cache);
80          DiskStore diskStore = (DiskStore)cache.getDiskStore();
81          return diskStore;
82      }
83  
84      private DiskStore createPersistentDiskStore(String cacheName) {
85          Cache cache = new Cache(cacheName, 10000, true, true, 5, 1, true, 600);
86          manager.addCache(cache);
87          DiskStore diskStore = (DiskStore)cache.getDiskStore();
88          return diskStore;
89      }
90  
91      private DiskStore createAutoPersistentDiskStore(String cacheName) {
92          Cache cache = new Cache(cacheName, 10000, true, true, 5, 1, true, 600);
93          manager2 = new CacheManager();
94          //manager.setDiskStorePath(System.getProperty("java.io.tmpdir") + File.separator + DiskStore.generateUniqueDirectory());
95          manager2.addCache(cache);
96          DiskStore diskStore = (DiskStore)cache.getDiskStore();
97          return diskStore;
98      }
99  
100     private DiskStore createPersistentDiskStoreFromCacheManager() {
101         Cache cache = manager.getCache("persistentLongExpiryIntervalCache");
102         return (DiskStore)cache.getDiskStore();
103     }
104 
105 
106     /**
107      * Test to help debug DiskStore test
108      */
109     public void testNothing() {
110         //just tests setup and teardown
111     }
112 
113     /**
114      * Tests that a file is created with the right size after puts, and that the file is
115      * deleted on disposal
116      */
117     public void testNonPersistentStore() throws IOException, InterruptedException {
118         DiskStore diskStore = createNonExpiringDiskStore();
119         File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
120 
121         for (int i = 0; i < 100; i++) {
122             byte[] data = new byte[1024];
123             diskStore.put(new Element("key" + (i + 100), data));
124         }
125         waitShorter();
126         assertEquals(ELEMENT_ON_DISK_SIZE * 100, diskStore.getDataFileSize());
127 
128         assertEquals(100, diskStore.getSize());
129         diskStore.dispose();
130         Thread.sleep(1);
131         assertFalse("File exists", dataFile.exists());
132     }
133 
134     /**
135      * Tests that a file is created with the right size after puts, and that the file is not
136      * deleted on disposal
137      * <p/>
138      * This test uses a preconfigured cache from the test cache.xml. Note that teardown causes
139      * an exception because the disk store is being shut down twice.
140      */
141     public void testPersistentStore() throws IOException, InterruptedException, CacheException {
142         //initialise
143         DiskStore diskStore = createPersistentDiskStoreFromCacheManager();
144         diskStore.removeAll();
145 
146         File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
147 
148         for (int i = 0; i < 100; i++) {
149             byte[] data = new byte[1024];
150             diskStore.put(new Element("key" + (i + 100), data));
151         }
152         waitShorter();
153         assertEquals(100, diskStore.getSize());
154         diskStore.dispose();
155 
156         assertTrue("File exists", dataFile.exists());
157         assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
158     }
159 
160     /**
161      * An integration test, at the CacheManager level, to make sure persistence works
162      */
163     public void testPersistentStoreFromCacheManager() throws IOException, InterruptedException, CacheException {
164         //initialise with an instance CacheManager so that the following line actually does something
165         CacheManager manager = new CacheManager(AbstractCacheTest.TEST_CONFIG_DIR + "ehcache-disk.xml");
166         Ehcache cache = manager.getCache("persistentLongExpiryIntervalCache");
167 
168         for (int i = 0; i < 100; i++) {
169             byte[] data = new byte[1024];
170             cache.put(new Element("key" + (i + 100), data));
171         }
172         assertEquals(100, cache.getSize());
173 
174         manager.shutdown();
175 
176         manager =  new CacheManager(AbstractCacheTest.TEST_CONFIG_DIR + "ehcache-disk.xml");
177         cache = manager.getCache("persistentLongExpiryIntervalCache");
178         assertEquals(100, cache.getSize());
179 
180     }
181 
182     /**
183      * Tests that the spool thread dies on dispose.
184      */
185     public void testSpoolThreadDiesOnDispose() throws IOException, InterruptedException {
186         Cache cache = new Cache("testNonPersistent", 10000, true, false, 5, 1, false, 100);
187         cache.initialise();
188         DiskStore diskStore = (DiskStore)cache.getDiskStore();
189 
190         //Put in some data
191         for (int i = 0; i < 100; i++) {
192             byte[] data = new byte[1024];
193             diskStore.put(new Element("key" + (i + 100), data));
194         }
195         waitShorter();
196 
197         diskStore.dispose();
198         //Give the spool thread time to be interrupted and die
199         Thread.sleep(100);
200         assertTrue(!diskStore.isSpoolThreadAlive());
201 
202 
203     }
204 
205     /**
206      * Tests that we can save and load a persistent store in a repeatable way
207      */
208     public void testLoadPersistentStore() throws IOException, InterruptedException {
209         //initialise
210         String cacheName = "testLoadPersistent";
211         DiskStore diskStore = createPersistentDiskStore(cacheName);
212         diskStore.removeAll();
213 
214 
215         for (int i = 0; i < 100; i++) {
216             byte[] data = new byte[1024];
217             diskStore.put(new Element("key" + (i + 100), data));
218         }
219         waitShorter();
220         assertEquals(ELEMENT_ON_DISK_SIZE * 100, diskStore.getDataFileSize());
221         assertEquals(100, diskStore.getSize());
222         manager.removeCache(cacheName);
223         Thread.sleep(3000);
224         //check that we can create and dispose several times with no problems and no lost data
225         for (int i = 0; i < 10; i++) {
226             diskStore = createPersistentDiskStore(cacheName);
227             File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
228             assertTrue("File exists", dataFile.exists());
229             assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
230             assertEquals(100, diskStore.getSize());
231 
232             manager.removeCache(cacheName);
233 
234             assertTrue("File exists", dataFile.exists());
235             assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
236         }
237     }
238 
239     /**
240      * Any disk store with an auto generated random directory should not be able to be loaded.
241      */
242     public void testCannotLoadPersistentStoreWithAutoDir() throws IOException, InterruptedException {
243         //initialise
244         String cacheName = "testPersistent";
245         DiskStore diskStore = createAutoPersistentDiskStore(cacheName);
246         diskStore.removeAll();
247 
248 
249         for (int i = 0; i < 100; i++) {
250             byte[] data = new byte[1024];
251             diskStore.put(new Element("key" + (i + 100), data));
252         }
253         waitLonger();
254         assertEquals(ELEMENT_ON_DISK_SIZE * 100, diskStore.getDataFileSize());
255         assertEquals(100, diskStore.getSize());
256         manager2.removeCache(cacheName);
257         Thread.sleep(1000);
258 
259         Cache cache = new Cache(cacheName, 10000, true, true, 5, 1, true, 600);
260         manager2.addCache(cache);
261 
262         File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
263         assertTrue("File exists", dataFile.exists());
264         assertEquals(0, dataFile.length());
265         assertEquals(0, cache.getSize());
266         manager.removeCache(cacheName);
267         assertTrue("File exists", dataFile.exists());
268         assertEquals(0, dataFile.length());
269     }
270 
271     /**
272      * Tests that we can save and load a persistent store in a repeatable way,
273      * and delete and add data.
274      */
275     public void testLoadPersistentStoreWithDelete() throws IOException, InterruptedException {
276         //initialise
277         String cacheName = "testPersistentWithDelete";
278         DiskStore diskStore = createPersistentDiskStore(cacheName);
279         diskStore.removeAll();
280 
281 
282         for (int i = 0; i < 100; i++) {
283             byte[] data = new byte[1024];
284             diskStore.put(new Element("key" + (i + 100), data));
285         }
286         waitShorter();
287         assertEquals(ELEMENT_ON_DISK_SIZE * 100, diskStore.getDataFileSize());
288         assertEquals(100, diskStore.getSize());
289         manager.removeCache(cacheName);
290 
291         diskStore = createPersistentDiskStore(cacheName);
292         File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
293         assertTrue("File exists", dataFile.exists());
294         assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
295         assertEquals(100, diskStore.getSize());
296 
297         diskStore.remove("key100");
298         assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
299         assertEquals(99, diskStore.getSize());
300 
301         manager.removeCache(cacheName);
302 
303         assertTrue("File exists", dataFile.exists());
304         assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
305     }
306 
307     /**
308      * Tests that we can load a store after the index has been corrupted
309      */
310     public void testLoadPersistentStoreAfterCorruption() throws IOException, InterruptedException {
311         //initialise
312         String cacheName = "testPersistent";
313         DiskStore diskStore = createPersistentDiskStore(cacheName);
314         diskStore.removeAll();
315 
316 
317         for (int i = 0; i < 100; i++) {
318             byte[] data = new byte[1024];
319             diskStore.put(new Element("key" + (i + 100), data));
320         }
321         waitShorter();
322         assertEquals(ELEMENT_ON_DISK_SIZE * 100, diskStore.getDataFileSize());
323         assertEquals(100, diskStore.getSize());
324         manager.removeCache(cacheName);
325 
326         File indexFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getIndexFileName());
327         FileOutputStream fout = new FileOutputStream(indexFile);
328         //corrupt the index file
329         fout.write(new byte[]{'q', 'w', 'e', 'r', 't', 'y'});
330         fout.close();
331         diskStore = createPersistentDiskStore(cacheName);
332         File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
333         assertTrue("File exists", dataFile.exists());
334 
335         //Make sure the data file got recreated since the index was corrupt
336         assertEquals("Data file was not recreated", 0, dataFile.length());
337         assertEquals(0, diskStore.getSize());
338     }
339 
340     /**
341      * Tests that we can save and load a persistent store in a repeatable way,
342      * and delete and add data.
343      */
344     public void testFreeSpaceBehaviour() throws IOException, InterruptedException {
345         //initialise
346         String cacheName = "testPersistent";
347         DiskStore diskStore = createPersistentDiskStore(cacheName);
348         diskStore.removeAll();
349 
350         byte[] data = new byte[1024];
351         for (int i = 0; i < 100; i++) {
352             diskStore.put(new Element("key" + (i + 100), data));
353         }
354         waitShorter();
355         assertEquals(ELEMENT_ON_DISK_SIZE * 100, diskStore.getDataFileSize());
356         assertEquals(100, diskStore.getSize());
357         manager.removeCache(cacheName);
358 
359         diskStore = createPersistentDiskStore(cacheName);
360         File dataFile = new File(diskStore.getDataFilePath() + File.separator + diskStore.getDataFileName());
361         assertTrue("File exists", dataFile.exists());
362         assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
363         assertEquals(100, diskStore.getSize());
364 
365         diskStore.remove("key100");
366         diskStore.remove("key101");
367         diskStore.remove("key102");
368         diskStore.remove("key103");
369         diskStore.remove("key104");
370 
371         diskStore.put(new Element("key100", data));
372         diskStore.put(new Element("key101", data));
373         waitShorter();
374 
375         //The file does not shrink.
376         assertEquals(100 * ELEMENT_ON_DISK_SIZE, dataFile.length());
377         assertEquals(97, diskStore.getSize());
378 
379         diskStore.put(new Element("key102", data));
380         diskStore.put(new Element("key103", data));
381         diskStore.put(new Element("key104", data));
382         diskStore.put(new Element("key201", data));
383         diskStore.put(new Element("key202", data));
384         waitShorter();
385         assertEquals(102 * ELEMENT_ON_DISK_SIZE, dataFile.length());
386         assertEquals(102, diskStore.getSize());
387         manager.removeCache(cacheName);
388         assertTrue("File exists", dataFile.exists());
389         assertEquals(102 * ELEMENT_ON_DISK_SIZE, dataFile.length());
390     }
391 
392     /**
393      * Tests looking up an entry that does not exist.
394      */
395     public void testGetUnknownThenKnown() throws Exception {
396         final DiskStore diskStore = createDiskStore();
397         Element element = diskStore.get("key");
398         assertNull(element);
399         diskStore.put(new Element("key", "value"));
400         element = diskStore.getQuiet("key");
401         assertNotNull(element);
402     }
403 
404     /**
405      * Tests looking up an entry that does not exist.
406      */
407     public void testGetQuietUnknownThenKnown() throws Exception {
408         final DiskStore diskStore = createDiskStore();
409         Element element = diskStore.getQuiet("key");
410         assertNull(element);
411         diskStore.put(new Element("key", "value"));
412         element = diskStore.getQuiet("key");
413         assertNotNull(element);
414     }
415 
416     /**
417      * Tests adding an entry.
418      */
419     public void testPut() throws Exception {
420         final DiskStore diskStore = createDiskStore();
421 
422         // Make sure the element is not found
423         assertEquals(0, diskStore.getSize());
424         Element element = diskStore.get("key");
425         assertNull(element);
426 
427         // Add the element
428         final String value = "value";
429         element = new Element("key", value);
430         diskStore.put(element);
431 
432         // Get the element
433         assertEquals(1, diskStore.getSize());
434         element = diskStore.get("key");
435         assertNotNull(element);
436         assertEquals(value, element.getObjectValue());
437     }
438 
439 
440     /**
441      *
442      */
443     public void testLFUEvictionFromDiskStore() throws IOException, InterruptedException {
444         Cache cache = new Cache("testNonPersistent", 0, MemoryStoreEvictionPolicy.LFU, true,
445                 null, false, 2000, 1000, false, 1, null, null, 10);
446         manager.addCache(cache);
447         DiskStore store = (DiskStore)cache.getDiskStore();
448 
449         Element element;
450 
451         assertEquals(0, store.getSize());
452 
453         for (int i = 0; i < 10; i++) {
454             element = new Element("key" + i, "value" + i);
455             cache.put(element);
456         }
457 
458         //allow to move through spool
459         Thread.sleep(210);
460         assertEquals(10, store.getSize());
461 
462 
463         for (int i = 1; i < 10; i++) {
464             cache.get("key" + i);
465             cache.get("key" + i);
466             cache.get("key" + i);
467             cache.get("key" + i);
468         }
469         //allow to move through spool
470         Thread.sleep(210);
471         assertEquals(10, store.getSize());
472 
473         element = new Element("keyNew", "valueNew");
474         store.put(element);
475 
476         //allow to get out of spool
477         Thread.sleep(210);
478         assertEquals(10, store.getSize());
479         //check new element not evicted
480         assertNotNull(store.get(element.getObjectKey()));
481         //check evicted honours LFU policy
482         assertNull(store.get("key0"));
483 
484         for (int i = 0; i < 2000; i++) {
485             store.put(new Element("" + i, new Date()));
486         }
487         //wait for spool to empty
488         waitLonger();
489 
490         assertEquals(10, store.getSize());
491     }
492 
493     /**
494      * Tests the loading of classes
495      */
496     public void testClassloading() throws Exception {
497         final DiskStore diskStore = createDiskStore();
498 
499         Long value = new Long(123L);
500         Element element = new Element("key", value);
501         diskStore.put(element);
502         Thread.sleep(1000);
503         Element elementOut = diskStore.get("key");
504         assertEquals(value, elementOut.getObjectValue());
505 
506 
507         Primitive primitive = new Primitive();
508         primitive.integerPrimitive = 123;
509         primitive.longPrimitive = 456L;
510         primitive.bytePrimitive = "a".getBytes()[0];
511         primitive.charPrimitive = 'B';
512         primitive.booleanPrimitive = false;
513 
514         //test Serializability
515         ByteArrayOutputStream outstr = new ByteArrayOutputStream();
516         ObjectOutputStream objstr = new ObjectOutputStream(outstr);
517         objstr.writeObject(element);
518         objstr.close();
519 
520 
521         Element primitiveElement = new Element("primitive", primitive);
522         diskStore.put(primitiveElement);
523         Thread.sleep(1000);
524         elementOut = diskStore.get("primitive");
525         assertEquals(primitive, elementOut.getObjectValue());
526 
527     }
528 
529 
530     /**
531      * Tests adding an entry and waiting for it to be written.
532      */
533     public void testPutSlow() throws Exception {
534         final DiskStore diskStore = createDiskStore();
535 
536         // Make sure the element is not found
537         assertEquals(0, diskStore.getSize());
538         Element element = diskStore.get("key");
539         assertNull(element);
540 
541         // Add the element
542         final String value = "value";
543         element = new Element("key", value);
544         diskStore.put(element);
545 
546         // Wait
547         waitShorter();
548 
549         // Get the element
550         assertEquals(1, diskStore.getSize());
551         element = diskStore.get("key");
552         assertNotNull(element);
553         assertEquals(value, element.getObjectValue());
554     }
555 
556     /**
557      * Tests removing an entry.
558      */
559     public void testRemove() throws Exception {
560         final DiskStore diskStore = createDiskStore();
561 
562         // Add the entry
563         final String value = "value";
564         Element element = new Element("key", value);
565         diskStore.put(element);
566 
567         // Check the entry is there
568         assertEquals(1, diskStore.getSize());
569         element = diskStore.get("key");
570         assertNotNull(element);
571 
572         // Remove it
573         diskStore.remove("key");
574 
575         // Check the entry is not there
576         assertEquals(0, diskStore.getSize());
577         element = diskStore.get("key");
578         assertNull(element);
579     }
580 
581     /**
582      * Tests removing an entry, after it has been written
583      */
584     public void testRemoveSlow() throws Exception {
585         final DiskStore diskStore = createDiskStore();
586 
587         // Add the entry
588         final String value = "value";
589         Element element = new Element("key", value);
590         diskStore.put(element);
591 
592         // Wait for the entry
593         waitShorter();
594 
595         // Check the entry is there
596         assertEquals(1, diskStore.getSize());
597         element = diskStore.get("key");
598         assertNotNull(element);
599 
600         // Remove it
601         diskStore.remove("key");
602 
603         // Check the entry is not there
604         assertEquals(0, diskStore.getSize());
605         element = diskStore.get("key");
606         assertNull(element);
607     }
608 
609     /**
610      * Tests removing all the entries.
611      */
612     public void testRemoveAll() throws Exception {
613         final DiskStore diskStore = createDiskStore();
614 
615         // Add the entry
616         final String value = "value";
617         Element element = new Element("key", value);
618         diskStore.put(element);
619 
620         // Check the entry is there
621         element = diskStore.get("key");
622         assertNotNull(element);
623 
624         // Remove it
625         diskStore.removeAll();
626 
627         // Check the entry is not there
628         assertEquals(0, diskStore.getSize());
629         element = diskStore.get("key");
630         assertNull(element);
631     }
632 
633     /**
634      * Tests removing all the entries, after they have been written to disk.
635      */
636     public void testRemoveAllSlow() throws Exception {
637         final DiskStore diskStore = createDiskStore();
638 
639         // Add the entry
640         final String value = "value";
641         Element element = new Element("key", value);
642         diskStore.put(element);
643 
644         // Wait
645         waitShorter();
646 
647         // Check the entry is there
648         element = diskStore.get("key");
649         assertNotNull(element);
650 
651         // Remove it
652         diskStore.removeAll();
653 
654         // Check the entry is not there
655         assertEquals(0, diskStore.getSize());
656         element = diskStore.get("key");
657         assertNull(element);
658     }
659 
660     /**
661      * Tests bulk load.
662      */
663     public void testBulkLoad() throws Exception {
664         final DiskStore diskStore = createDiskStore();
665 
666         final Random random = new Random();
667 
668         // Add a bunch of entries
669         for (int i = 0; i < 500; i++) {
670             // Use a random length value
671             final String key = "key" + i;
672             final String value = "This is a value" + random.nextInt(1000);
673 
674             // Add an element, and make sure it is present
675             Element element = new Element(key, value);
676             diskStore.put(element);
677             element = diskStore.get(key);
678             assertNotNull(element);
679 
680             // Chuck in a delay, to give the spool thread a chance to catch up
681             Thread.sleep(2);
682 
683             // Remove the element
684             diskStore.remove(key);
685             element = diskStore.get(key);
686             assertNull(element);
687 
688             element = new Element(key, value);
689             diskStore.put(element);
690             element = diskStore.get(key);
691             assertNotNull(element);
692 
693             // Chuck in a delay
694             Thread.sleep(2);
695         }
696     }
697 
698     /**
699      * Tests for element expiry.
700      */
701     public void testExpiry() throws Exception {
702         // Create a diskStore with a cranked up expiry thread
703         final DiskStore diskStore = createDiskStore();
704 
705         // Add an element that will expire.
706         Element element = new Element("key", "value");
707         diskStore.put(element);
708         assertEquals(1, diskStore.getSize());
709 
710         assertNotNull(diskStore.get("key"));
711 
712         // Wait a couple of seconds
713         Thread.sleep(3000);
714 
715         assertNull(diskStore.get("key"));
716 
717     }
718 
719     /**
720      * Checks that the expiry thread runs and expires elements which has the effect
721      * of preventing the disk store from continously growing.
722      * Ran for 6 hours through 10000 outer loops. No memory use increase.
723      * Using a key of "key" + i * outer) you get early slots that cannot be reused. The DiskStore
724      * actual size therefore starts at 133890 and ends at 616830. There is quite a lot of space
725      * that cannot be used because of fragmentation. Question? Should an effort be made to coalesce
726      * fragmented space? Unlikely in production to get contiguous fragments as in the first form
727      * of this test.
728      * <p/>
729      * Using a key of new Integer(i * outer) the size stays constant at 140800.
730      *
731      * @throws InterruptedException
732      */
733     public void testExpiryWithSize() throws InterruptedException {
734         DiskStore diskStore = createDiskStore();
735         diskStore.removeAll();
736 
737         byte[] data = new byte[1024];
738         for (int outer = 1; outer <= 10; outer++) {
739             for (int i = 0; i < 100; i++) {
740                 Element element = new Element(new Integer(i * outer), data);
741                 element.setTimeToLive(1);
742                 diskStore.put(element);
743             }
744             waitLonger();
745             int predictedSize = 140800;
746             long actualSize = diskStore.getDataFileSize();
747             LOG.info("Predicted Size: " + predictedSize + " Actual Size: " + actualSize);
748             assertEquals(predictedSize, actualSize);
749             LOG.info("Memory Use: " + measureMemoryUse());
750         }
751 
752 
753     }
754 
755     /**
756      * Waits for all spooled elements to be written to disk.
757      */
758     private static void waitShorter() throws InterruptedException {
759         Thread.sleep((long) (300 + 100 * StopWatch.getSpeedAdjustmentFactor()));
760     }
761 
762 
763     /**
764      * Waits for all spooled elements to be written to disk.
765      */
766     private static void waitLonger() throws InterruptedException {
767         Thread.sleep((long) (300 + 500 * StopWatch.getSpeedAdjustmentFactor()));
768     }
769 
770 
771     /**
772      * Multi-thread read-only test. Will fail on memory constrained VMs
773      */
774     public void testReadOnlyMultipleThreads() throws Exception {
775         final DiskStore diskStore = createNonExpiringDiskStore();
776 
777         // Add a couple of elements
778         diskStore.put(new Element("key0", "value"));
779         diskStore.put(new Element("key1", "value"));
780 
781         // Wait for the elements to be written
782         waitShorter();
783 
784         // Run a set of threads, that attempt to fetch the elements
785         final List executables = new ArrayList();
786         for (int i = 0; i < 10; i++) {
787             final String key = "key" + (i % 2);
788             final Executable executable = new Executable() {
789                 public void execute() throws Exception {
790                     final Element element = diskStore.get(key);
791                     assertNotNull(element);
792                     assertEquals("value", element.getObjectValue());
793                 }
794             };
795             executables.add(executable);
796         }
797         runThreads(executables);
798     }
799 
800     /**
801      * Multi-thread concurrent read remove test.
802      */
803     public void testReadRemoveMultipleThreads() throws Exception {
804         final Random random = new Random();
805         final DiskStore diskStore = createDiskStore();
806 
807         diskStore.put(new Element("key", "value"));
808 
809         // Run a set of threads that get, put and remove an entry
810         final List executables = new ArrayList();
811         for (int i = 0; i < 5; i++) {
812             final Executable executable = new Executable() {
813                 public void execute() throws Exception {
814                     for (int i = 0; i < 100; i++) {
815                         diskStore.put(new Element("key" + random.nextInt(100), "value"));
816                     }
817                 }
818             };
819             executables.add(executable);
820         }
821         for (int i = 0; i < 5; i++) {
822             final Executable executable = new Executable() {
823                 public void execute() throws Exception {
824                     for (int i = 0; i < 100; i++) {
825                         diskStore.remove("key" + random.nextInt(100));
826                     }
827                 }
828             };
829             executables.add(executable);
830         }
831 
832         runThreads(executables);
833     }
834 
835     /**
836      * Tests how data is written to a random access file.
837      * <p/>
838      * It makes sure that bytes are immediately written to disk after a write.
839      */
840     public void testWriteToFile() throws IOException {
841         // Create and set up file
842         String dataFileName = "fileTest";
843         RandomAccessFile file = getRandomAccessFile(dataFileName);
844 
845         //write data to the file
846         byte[] buffer = new byte[1024];
847         for (int i = 0; i < 100; i++) {
848             file.write(buffer);
849         }
850 
851         assertEquals(1024 * 100, file.length());
852 
853     }
854 
855     private RandomAccessFile getRandomAccessFile(String name) throws FileNotFoundException {
856         String diskPath = System.getProperty("java.io.tmpdir");
857         final File diskDir = new File(diskPath);
858         File dataFile = new File(diskDir, name + ".data");
859         return new RandomAccessFile(dataFile, "rw");
860     }
861 
862     /**
863      * Test overflow to disk = true, using 100000 records.
864      * 15 seconds v1.38 DiskStore
865      * 2 seconds v1.42 DiskStore
866      * Adjusted for change to laptop
867      */
868     public void testOverflowToDiskWithLargeNumberofCacheEntries() throws Exception {
869 
870         //Set size so the second element overflows to disk.
871         Cache cache = new Cache("test", 1000, MemoryStoreEvictionPolicy.LRU, true, null, true, 500, 500, false, 1, null);
872         manager.addCache(cache);
873         int i = 0;
874         StopWatch stopWatch = new StopWatch();
875         for (; i < 100000; i++) {
876             cache.put(new Element("" + i,
877                     "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
878                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
879                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
880                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
881                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
882         }
883         long time = stopWatch.getElapsedTime();
884         assertTrue(4 < time);
885     }
886 
887 
888     /**
889      * This test is designed to be used with a profiler to explore the ways in which DiskStore
890      * uses memory. It does not do much on its own.
891      */
892     public void testOutOfMemoryErrorOnOverflowToDisk() throws Exception {
893 
894         //Set size so the second element overflows to disk.
895         Cache cache = new Cache("test", 1000, MemoryStoreEvictionPolicy.LRU, true, null, true, 500, 500, false, 1, null);
896         manager.addCache(cache);
897         int i = 0;
898 
899         Random random = new Random();
900         for (; i < 5500; i++) {
901             byte[] bytes = new byte[10000];
902             random.nextBytes(bytes);
903             cache.put(new Element("" + i, bytes));
904         }
905         LOG.info("Elements written: " + i);
906         //Thread.sleep(100000);
907     }
908 
909     /**
910      * Test overflow to disk = true, using 100000 records.
911      * 35 seconds v1.38 DiskStore
912      * 26 seconds v1.42 DiskStore
913      */
914     public void testOverflowToDiskWithLargeNumberofCacheEntriesAndGets() throws Exception {
915 
916         //Set size so the second element overflows to disk.
917         Cache cache = new Cache("test", 1000, MemoryStoreEvictionPolicy.LRU, true, null, true, 500, 500, false, 60, null);
918         manager.addCache(cache);
919         Random random = new Random();
920         StopWatch stopWatch = new StopWatch();
921         for (int i = 0; i < 100000; i++) {
922             cache.put(new Element("" + i,
923                     "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
924                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
925                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
926                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
927                             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
928 
929             cache.get("" + random.nextInt(100000));
930         }
931 
932 
933         long elapsed = stopWatch.getElapsedTime();
934         LOG.info("Elapsed time: " + elapsed / 1000);
935         assertEquals(100000, cache.getSize());
936         assertTrue(23 < elapsed);
937         //Some entries may be in the Memory Store and Disk Store. cache.getSize removes dupes. a look at the
938         //disk store size directly does not.
939         assertTrue(99000 <= cache.getDiskStore().getSize());
940     }
941 
942     /**
943      * Runs out of memory at 5,099,999 elements with the standard 64MB VM size on 32 bit architectures.
944      * Around 3,099,999 for AMD64. Why? See abstract citation below from
945      * http://www3.interscience.wiley.com/cgi-bin/abstract/111082816/ABSTRACT?CRETRY=1&SRETRY=0
946      * <pre>
947      * By running the PowerPC machine in both 32-bit and 64-bit mode we are able to compare 32-bit and 64-bit VMs.
948      * We conclude that the space an object takes in the heap in 64-bit mode is 39.3% larger on average than in
949      * 32-bit mode. We identify three reasons for this: (i) the larger pointer size, (ii) the increased header
950      * and (iii) the increased alignment. The minimally required heap size is 51.1% larger on average in 64-bit
951      * than in 32-bit mode. From our experimental setup using hardware performance monitors, we observe that 64-bit
952      * computing typically results in a significantly larger number of data cache misses at all levels of the memory
953      * hierarchy. In addition, we observe that when a sufficiently large heap is available, the IBM JDK 1.4.0 VM is
954      * 1.7% slower on average in 64-bit mode than in 32-bit mode. Copyright © 2005 John Wiley & Sons, Ltd.
955      * </pre>
956      * The reason that it is not infinite is because of a small amount of memory used (about 12 bytes) used for
957      * the disk store index in this case.
958      * <p/>
959      * Slow tests
960      */
961     public void testMaximumCacheEntriesIn64MBWithOverflowToDisk() throws Exception {
962 
963         Cache cache = new Cache("test", 1000, MemoryStoreEvictionPolicy.LRU, true, null, true, 500, 500, false, 1, null);
964         manager.addCache(cache);
965         StopWatch stopWatch = new StopWatch();
966         int i = 0;
967         int j = 0;
968         Integer index = null;
969         try {
970             for (; i < 100; i++) {
971                 for (j = 0; j < 100000; j++) {
972                     index = new Integer(((1000000 * i) + j));
973                     cache.put(new Element(index,
974                             "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
975                                     + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
976                                     + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
977                                     + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
978                                     + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
979                 }
980                 //wait to write entries
981                 int size = cache.getSize();
982                 Thread.sleep(2000);
983             }
984             long elapsed = stopWatch.getElapsedTime();
985             LOG.info("Elapsed time: " + elapsed / 1000);
986             fail();
987         } catch (OutOfMemoryError e) {
988             LOG.info("All heap consumed after " + index + " entries created.");
989             int expectedMax = 3090000;
990             assertTrue("Achieved " + index.intValue() + " which was less than the expected value of " + expectedMax,
991                     index.intValue() >= expectedMax);
992         }
993     }
994 
995     /**
996      * Perf test used by Venkat Subramani
997      * Get took 119s with Cache svn21
998      * Get took 42s
999      * The change was to stop adding DiskStore retrievals into the MemoryStore. This made sense when the only
1000      * policy was LRU. In the new version an Elment, once evicted from the MemoryStore, stays in the DiskStore
1001      * until expiry or removal. This avoids a lot of serialization overhead.
1002      * <p/>
1003      * Slow tests
1004      * 235 with get. 91 for 1.2.3. 169 with remove.
1005      */
1006     public void xTestLargePutGetPerformanceWithOverflowToDisk() throws Exception {
1007 
1008         Cache cache = new Cache("test", 1000, MemoryStoreEvictionPolicy.LRU, true, null, true, 500, 500, false, 10000, null);
1009         manager.addCache(cache);
1010         StopWatch stopWatch = new StopWatch();
1011         int i = 0;
1012         int j = 0;
1013         Integer index;
1014         for (; i < 5; i++) {
1015             for (j = 0; j < 100000; j++) {
1016                 index = new Integer(((1000000 * i) + j));
1017                 cache.put(new Element(index,
1018                         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1019                                 + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1020                                 + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1021                                 + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1022                                 + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
1023             }
1024             //wait to write entries
1025             Thread.sleep(2000);
1026         }
1027         long elapsed = stopWatch.getElapsedTime();
1028         long putTime = ((elapsed / 1000) - 10);
1029         LOG.info("Put Elapsed time: " + putTime);
1030         assertTrue(putTime < 15);
1031 
1032         //wait for Disk Store to finish spooling
1033         Thread.sleep(2000);
1034         Random random = new Random();
1035         StopWatch getStopWatch = new StopWatch();
1036         long getStart = stopWatch.getElapsedTime();
1037 
1038         for (int k = 0; k < 1000000; k++) {
1039             Integer key = new Integer(random.nextInt(500000));
1040             cache.get(key);
1041         }
1042 
1043         long getElapsedTime = getStopWatch.getElapsedTime();
1044         int time = (int) ((getElapsedTime - getStart) / 1000);
1045         LOG.info("Get Elapsed time: " + time);
1046 
1047         assertTrue(time < 180);
1048 
1049 
1050     }
1051 
1052     /**
1053      * Java is not consistent with trailing file separators, believe it or not!
1054      * http://www.rationalpi.com/blog/replyToComment.action?entry=1146628709626&comment=1155660875090
1055      * Can we fix c:\temp\\greg?
1056      */
1057     public void testWindowsAndSolarisTempDirProblem() throws InterruptedException {
1058 
1059         String originalPath = "c:" + File.separator + "temp" + File.separator + File.separator + "greg";
1060         //Fix dup separator
1061         String translatedPath = DiskStoreConfiguration.replaceToken(File.separator + File.separator,
1062                 File.separator, originalPath);
1063         assertEquals("c:" + File.separator + "temp" + File.separator + "greg", translatedPath);
1064         //Ignore single separators
1065         translatedPath = DiskStoreConfiguration.replaceToken(File.separator + File.separator, File.separator, originalPath);
1066         assertEquals("c:" + File.separator + "temp" + File.separator + "greg", translatedPath);
1067 
1068         Thread.sleep(500);
1069     }
1070 }