1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package net.sf.ehcache.store;
19
20 import net.sf.ehcache.CacheException;
21 import net.sf.ehcache.Ehcache;
22 import net.sf.ehcache.Element;
23 import net.sf.ehcache.Status;
24 import net.sf.ehcache.event.RegisteredEventListeners;
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.ObjectInputStream;
35 import java.io.ObjectOutputStream;
36 import java.io.ObjectStreamClass;
37 import java.io.RandomAccessFile;
38 import java.io.Serializable;
39 import java.io.StreamCorruptedException;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 public class DiskStore implements Store {
66
67
68
69
70
71
72
73 public static final String AUTO_DISK_PATH_DIRECTORY_PREFIX = "ehcache_auto_created";
74
75 private static final Log LOG = LogFactory.getLog(DiskStore.class.getName());
76 private static final int MS_PER_SECOND = 1000;
77 private static final int SPOOL_THREAD_INTERVAL = 200;
78 private static final int ESTIMATED_MINIMUM_PAYLOAD_SIZE = 512;
79 private static final int ONE_MEGABYTE = 1048576;
80
81 private long expiryThreadInterval;
82
83 private final String name;
84 private boolean active;
85 private RandomAccessFile randomAccessFile;
86
87 private Map diskElements = Collections.synchronizedMap(new HashMap());
88 private List freeSpace = Collections.synchronizedList(new ArrayList());
89 private Map spool = new HashMap();
90 private Object spoolLock = new Object();
91
92 private Thread spoolAndExpiryThread;
93
94 private Ehcache cache;
95
96
97
98
99
100
101
102 private final boolean persistent;
103
104 private final String diskPath;
105
106 private File dataFile;
107
108
109
110
111
112 private File indexFile;
113
114 private Status status;
115
116
117
118
119 private long totalSize;
120
121
122
123
124 private final long maxElementsOnDisk;
125
126
127
128 private boolean eternal;
129 private int lastElementSize;
130 private int diskSpoolBufferSizeBytes;
131
132
133
134
135
136
137
138
139 public DiskStore(Ehcache cache, String diskPath) {
140 status = Status.STATUS_UNINITIALISED;
141 this.cache = cache;
142 name = cache.getName();
143 this.diskPath = diskPath;
144 expiryThreadInterval = cache.getDiskExpiryThreadIntervalSeconds();
145 persistent = cache.isDiskPersistent();
146 maxElementsOnDisk = cache.getMaxElementsOnDisk();
147 eternal = cache.isEternal();
148 diskSpoolBufferSizeBytes = cache.getCacheConfiguration().getDiskSpoolBufferSizeMB() * ONE_MEGABYTE;
149
150
151
152 try {
153 initialiseFiles();
154
155 active = true;
156
157
158 spoolAndExpiryThread = new SpoolAndExpiryThread();
159 spoolAndExpiryThread.start();
160
161 status = Status.STATUS_ALIVE;
162 } catch (final Exception e) {
163
164 dispose();
165 LOG.error(name + "Cache: Could not create disk store. Initial cause was " + e.getMessage(), e);
166 }
167 }
168
169
170 private void initialiseFiles() throws Exception {
171
172 final File diskDir = new File(diskPath);
173 if (diskDir.exists() && !diskDir.isDirectory()) {
174 throw new Exception("Store directory \"" + diskDir.getCanonicalPath() + "\" exists and is not a directory.");
175 }
176 if (!diskDir.exists() && !diskDir.mkdirs()) {
177 throw new Exception("Could not create cache directory \"" + diskDir.getCanonicalPath() + "\".");
178 }
179
180 dataFile = new File(diskDir, getDataFileName());
181 indexFile = new File(diskDir, getIndexFileName());
182
183 deleteIndexIfNoData();
184
185 if (persistent) {
186
187 if (diskPath.indexOf(AUTO_DISK_PATH_DIRECTORY_PREFIX) != -1) {
188 LOG.warn("Data in persistent disk stores is ignored for stores from automatically created directories"
189 + " (they start with " + AUTO_DISK_PATH_DIRECTORY_PREFIX + ").\n"
190 + "Remove diskPersistent or resolve the conflicting disk paths in cache configuration.\n"
191 + "Deleting data file " + getDataFileName());
192 dataFile.delete();
193 } else if (!readIndex()) {
194 if (LOG.isDebugEnabled()) {
195 LOG.debug("Index file dirty or empty. Deleting data file " + getDataFileName());
196 }
197 dataFile.delete();
198 }
199 } else {
200 if (LOG.isDebugEnabled()) {
201 LOG.debug("Deleting data file " + getDataFileName());
202 }
203 dataFile.delete();
204 indexFile = null;
205 }
206
207
208 randomAccessFile = new RandomAccessFile(dataFile, "rw");
209 }
210
211 private void deleteIndexIfNoData() {
212 boolean dataFileExists = dataFile.exists();
213 boolean indexFileExists = indexFile.exists();
214 if (!dataFileExists && indexFileExists) {
215 if (LOG.isDebugEnabled()) {
216 LOG.debug("Matching data file missing for index file. Deleting index file " + getIndexFileName());
217 }
218 indexFile.delete();
219 }
220 }
221
222
223
224
225 private void checkActive() throws CacheException {
226 if (!active) {
227 throw new CacheException(name + " Cache: The Disk store is not active.");
228 }
229 }
230
231
232
233
234
235
236 public final synchronized Element get(final Object key) {
237 try {
238 checkActive();
239
240
241 Element element;
242 synchronized (spoolLock) {
243 element = (Element) spool.remove(key);
244 }
245 if (element != null) {
246 element.updateAccessStatistics();
247 return element;
248 }
249
250
251 final DiskElement diskElement = (DiskElement) diskElements.get(key);
252 if (diskElement == null) {
253
254 return null;
255 }
256
257 element = loadElementFromDiskElement(diskElement);
258 element.updateAccessStatistics();
259 return element;
260 } catch (Exception exception) {
261 LOG.error(name + "Cache: Could not read disk store element for key " + key + ". Error was "
262 + exception.getMessage(), exception);
263 }
264 return null;
265 }
266
267
268
269
270
271
272
273
274 public final boolean containsKey(Object key) {
275 return diskElements.containsKey(key) || spool.containsKey(key);
276 }
277
278 private Element loadElementFromDiskElement(DiskElement diskElement) throws IOException, ClassNotFoundException {
279 Element element;
280
281 randomAccessFile.seek(diskElement.position);
282 final byte[] buffer = new byte[diskElement.payloadSize];
283 randomAccessFile.readFully(buffer);
284 final ByteArrayInputStream instr = new ByteArrayInputStream(buffer);
285
286 final ObjectInputStream objstr = new ObjectInputStream(instr) {
287
288
289
290
291 protected Class resolveClass(ObjectStreamClass clazz) throws ClassNotFoundException, IOException {
292 try {
293 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
294 return Class.forName(clazz.getName(), false, classLoader);
295 } catch (ClassNotFoundException e) {
296
297
298 return super.resolveClass(clazz);
299 }
300 }
301 };
302 element = (Element) objstr.readObject();
303 objstr.close();
304 return element;
305 }
306
307
308
309
310
311
312 public final synchronized Element getQuiet(final Object key) {
313 try {
314 checkActive();
315
316
317 Element element;
318 synchronized (spoolLock) {
319 element = (Element) spool.remove(key);
320 }
321 if (element != null) {
322
323 return element;
324 }
325
326
327 final DiskElement diskElement = (DiskElement) diskElements.get(key);
328 if (diskElement == null) {
329
330 return null;
331 }
332
333 element = loadElementFromDiskElement(diskElement);
334
335 return element;
336 } catch (Exception e) {
337 LOG.error(name + "Cache: Could not read disk store element for key " + key
338 + ". Initial cause was " + e.getMessage(), e);
339 }
340 return null;
341 }
342
343
344
345
346
347
348
349
350 public final synchronized Object[] getKeyArray() {
351 Set elementKeySet;
352 synchronized (diskElements) {
353 elementKeySet = diskElements.keySet();
354 }
355 Set spoolKeySet;
356 synchronized (spoolLock) {
357 spoolKeySet = spool.keySet();
358 }
359 Set allKeysSet = new HashSet(elementKeySet.size() + spoolKeySet.size());
360 allKeysSet.addAll(elementKeySet);
361 allKeysSet.addAll(spoolKeySet);
362 return allKeysSet.toArray();
363 }
364
365
366
367
368
369
370 public final synchronized int getSize() {
371 try {
372 checkActive();
373 int spoolSize;
374 synchronized (spoolLock) {
375 spoolSize = spool.size();
376 }
377 int diskSize;
378 synchronized (diskElements) {
379 diskSize = diskElements.size();
380 }
381 return spoolSize + diskSize;
382 } catch (Exception e) {
383 LOG.error(name + "Cache: Could not determine size of disk store.. Initial cause was " + e.getMessage(), e);
384 return 0;
385 }
386 }
387
388
389
390
391 public final Status getStatus() {
392 return status;
393 }
394
395
396
397
398
399
400
401 public final void put(final Element element) {
402 try {
403 checkActive();
404
405
406 if (spoolAndExpiryThread.isAlive()) {
407 synchronized (spoolLock) {
408 spool.put(element.getObjectKey(), element);
409 }
410 } else {
411 LOG.error(name + "Cache: Elements cannot be written to disk store because the" +
412 " spool thread has died.");
413 synchronized (spoolLock) {
414 spool.clear();
415 }
416 }
417
418 } catch (Exception e) {
419 LOG.error(name + "Cache: Could not write disk store element for " + element.getObjectKey()
420 + ". Initial cause was " + e.getMessage(), e);
421 }
422 }
423
424
425
426
427
428
429
430
431
432 public boolean backedUp() {
433 long estimatedSpoolSize = spool.size() * lastElementSize;
434 boolean backedUp = estimatedSpoolSize > diskSpoolBufferSizeBytes;
435 if (backedUp && LOG.isTraceEnabled()) {
436 LOG.trace("A back up on cache puts occurred. Consider increasing diskSpoolBufferSizeMB for cache " + name);
437 }
438 return backedUp;
439
440 }
441
442
443
444
445
446
447 public final synchronized Element remove(final Object key) {
448 Element element;
449 try {
450 checkActive();
451
452
453 synchronized (spoolLock) {
454 element = (Element) spool.remove(key);
455 }
456
457
458 synchronized (diskElements) {
459 final DiskElement diskElement = (DiskElement) diskElements.remove(key);
460 if (diskElement != null) {
461 element = loadElementFromDiskElement(diskElement);
462 freeBlock(diskElement);
463 }
464 }
465 } catch (Exception exception) {
466 String message = name + "Cache: Could not remove disk store entry for " + key
467 + ". Error was " + exception.getMessage();
468 LOG.error(message, exception);
469 throw new CacheException(message);
470 }
471 return element;
472 }
473
474
475
476
477
478
479 private void freeBlock(final DiskElement diskElement) {
480 totalSize -= diskElement.payloadSize;
481 diskElement.payloadSize = 0;
482
483
484 diskElement.key = null;
485 diskElement.hitcount = 0;
486 diskElement.expiryTime = 0;
487
488 freeSpace.add(diskElement);
489 }
490
491
492
493
494
495
496
497 public final synchronized void removeAll() {
498 try {
499 checkActive();
500
501
502 spool = Collections.synchronizedMap(new HashMap());
503 diskElements = Collections.synchronizedMap(new HashMap());
504 freeSpace = Collections.synchronizedList(new ArrayList());
505 totalSize = 0;
506 randomAccessFile.setLength(0);
507 if (persistent) {
508 indexFile.delete();
509 indexFile.createNewFile();
510 }
511 } catch (Exception e) {
512
513 LOG.error(name + " Cache: Could not rebuild disk store. Initial cause was " + e.getMessage(), e);
514 dispose();
515 }
516 }
517
518
519
520
521
522
523
524
525
526 public final synchronized void dispose() {
527
528 if (!active) {
529 return;
530 }
531
532
533 try {
534
535 flush();
536
537
538 if (spoolAndExpiryThread != null) {
539 spoolAndExpiryThread.interrupt();
540 }
541
542
543 spool.clear();
544 diskElements.clear();
545 freeSpace.clear();
546 if (randomAccessFile != null) {
547 randomAccessFile.close();
548 }
549 if (!persistent) {
550 LOG.debug("Deleting file " + dataFile.getName());
551 dataFile.delete();
552 }
553 } catch (Exception e) {
554 LOG.error(name + "Cache: Could not shut down disk cache. Initial cause was " + e.getMessage(), e);
555 } finally {
556 active = false;
557 randomAccessFile = null;
558 notifyAll();
559
560
561 cache = null;
562 }
563 }
564
565
566
567
568
569
570 public final void flush() throws IOException {
571 if (persistent) {
572 flushSpool();
573 writeIndex();
574 }
575 }
576
577
578
579
580
581
582
583
584 private void spoolAndExpiryThreadMain() {
585 long nextExpiryTime = System.currentTimeMillis();
586 while (true) {
587
588 try {
589 Thread.sleep(SPOOL_THREAD_INTERVAL);
590 } catch (InterruptedException e) {
591 LOG.debug("Spool Thread interrupted.");
592 return;
593 }
594
595 if (!active) {
596 return;
597 }
598
599 throwableSafeFlushSpoolIfRequired();
600
601 if (!active) {
602 return;
603 }
604 nextExpiryTime = throwableSafeExpireElementsIfRequired(nextExpiryTime);
605
606
607 }
608 }
609
610 private long throwableSafeExpireElementsIfRequired(long nextExpiryTime) {
611
612 long updatedNextExpiryTime = nextExpiryTime;
613
614
615 if (!eternal && System.currentTimeMillis() > nextExpiryTime) {
616 try {
617 updatedNextExpiryTime += expiryThreadInterval * MS_PER_SECOND;
618 expireElements();
619 } catch (Throwable e) {
620 LOG.error(name + " Cache: Could not expire elements from disk due to "
621 + e.getMessage() + ". Continuing...", e);
622 }
623 }
624 return updatedNextExpiryTime;
625 }
626
627 private void throwableSafeFlushSpoolIfRequired() {
628 if (spool != null && spool.size() != 0) {
629
630 try {
631 flushSpool();
632 } catch (Throwable e) {
633 LOG.error(name + " Cache: Could not flush elements to disk due to " + e.getMessage() + ". Continuing...", e);
634 }
635 }
636 }
637
638
639
640
641
642
643
644
645 private synchronized void flushSpool() throws IOException {
646 if (spool.size() == 0) {
647 return;
648 }
649
650 Map copyOfSpool = swapSpoolReference();
651
652
653 Iterator valuesIterator = copyOfSpool.values().iterator();
654 while (valuesIterator.hasNext()) {
655 writeOrReplaceEntry(valuesIterator.next());
656 valuesIterator.remove();
657 }
658 }
659
660 private Map swapSpoolReference() {
661 Map copyOfSpool = null;
662 synchronized (spoolLock) {
663
664 copyOfSpool = spool;
665
666
667 spool = Collections.synchronizedMap(new HashMap());
668 }
669 return copyOfSpool;
670 }
671
672
673 private void writeOrReplaceEntry(Object object) throws IOException {
674 Element element = (Element) object;
675 if (element == null) {
676 return;
677 }
678 final Serializable key = (Serializable) element.getObjectKey();
679 removeOldEntryIfAny(key);
680 if (maxElementsOnDisk > 0 && diskElements.size() >= maxElementsOnDisk) {
681 evictLfuDiskElement();
682 }
683 writeElement(element, key);
684 }
685
686
687 private void writeElement(Element element, Serializable key) throws IOException {
688 try {
689 int bufferLength;
690 long expirationTime = element.getExpirationTime();
691
692 MemoryEfficientByteArrayOutputStream buffer = null;
693 try {
694 buffer = serializeEntry(element);
695
696 bufferLength = buffer.size();
697 DiskElement diskElement = checkForFreeBlock(bufferLength);
698
699
700 randomAccessFile.seek(diskElement.position);
701 randomAccessFile.write(buffer.toByteArray(), 0, bufferLength);
702 buffer = null;
703
704
705 diskElement.payloadSize = bufferLength;
706 diskElement.key = key;
707 diskElement.expiryTime = expirationTime;
708 diskElement.hitcount = element.getHitCount();
709 totalSize += bufferLength;
710 lastElementSize = bufferLength;
711 synchronized (diskElements) {
712 diskElements.put(key, diskElement);
713 }
714 } catch (OutOfMemoryError e) {
715 LOG.error("OutOfMemoryError on serialize: " + key);
716
717 }
718
719 } catch (Exception e) {
720
721 LOG.error(name + "Cache: Failed to write element to disk '" + key
722 + "'. Initial cause was " + e.getMessage(), e);
723 }
724
725 }
726
727
728
729
730
731 class MemoryEfficientByteArrayOutputStream extends ByteArrayOutputStream {
732
733
734
735
736
737
738
739
740 public MemoryEfficientByteArrayOutputStream(int size) {
741 super(size);
742 }
743
744
745
746
747
748
749 public synchronized byte getBytes()[] {
750 return buf;
751 }
752 }
753
754 private MemoryEfficientByteArrayOutputStream serializeEntry(Element element) throws IOException {
755 MemoryEfficientByteArrayOutputStream outstr = new MemoryEfficientByteArrayOutputStream(estimatedPayloadSize());
756 ObjectOutputStream objstr = new ObjectOutputStream(outstr);
757 objstr.writeObject(element);
758 objstr.close();
759 return outstr;
760 }
761
762
763 private int estimatedPayloadSize() {
764 int size = 0;
765 try {
766 size = (int) (totalSize / diskElements.size());
767 } catch (Exception e) {
768
769 }
770 if (size <= 0) {
771 size = ESTIMATED_MINIMUM_PAYLOAD_SIZE;
772 }
773 return size;
774 }
775
776
777
778
779
780
781 private void removeOldEntryIfAny(Serializable key) {
782
783 final DiskElement oldBlock;
784 synchronized (diskElements) {
785 oldBlock = (DiskElement) diskElements.remove(key);
786 }
787 if (oldBlock != null) {
788 freeBlock(oldBlock);
789 }
790 }
791
792 private DiskElement checkForFreeBlock(int bufferLength) throws IOException {
793 DiskElement diskElement = findFreeBlock(bufferLength);
794 if (diskElement == null) {
795 diskElement = new DiskElement();
796 diskElement.position = randomAccessFile.length();
797 diskElement.blockSize = bufferLength;
798 }
799 return diskElement;
800 }
801
802
803
804
805
806
807
808
809 private synchronized void writeIndex() throws IOException {
810
811 ObjectOutputStream objectOutputStream = null;
812 try {
813 FileOutputStream fout = new FileOutputStream(indexFile);
814 objectOutputStream = new ObjectOutputStream(fout);
815 objectOutputStream.writeObject(diskElements);
816 objectOutputStream.writeObject(freeSpace);
817 } finally {
818 if (objectOutputStream != null) {
819 objectOutputStream.close();
820 }
821 }
822 }
823
824
825
826
827
828
829
830
831
832
833 private synchronized boolean readIndex() throws IOException {
834 ObjectInputStream objectInputStream = null;
835 FileInputStream fin = null;
836 boolean success = false;
837 if (indexFile.exists()) {
838 try {
839 fin = new FileInputStream(indexFile);
840 objectInputStream = new ObjectInputStream(fin);
841 diskElements = (Map) objectInputStream.readObject();
842 freeSpace = (List) objectInputStream.readObject();
843 success = true;
844 } catch (StreamCorruptedException e) {
845 LOG.error("Corrupt index file. Creating new index.");
846 } catch (IOException e) {
847
848 if (LOG.isDebugEnabled()) {
849 LOG.debug("IOException reading index. Creating new index. ");
850 }
851 } catch (ClassNotFoundException e) {
852 LOG.error("Class loading problem reading index. Creating new index. Initial cause was " + e.getMessage(), e);
853 } finally {
854 try {
855 if (objectInputStream != null) {
856 objectInputStream.close();
857 } else if (fin != null) {
858 fin.close();
859 }
860 } catch (IOException e) {
861 LOG.error("Problem closing the index file.");
862 }
863
864
865
866
867 createNewIndexFile();
868 }
869 } else {
870 createNewIndexFile();
871 }
872
873
874 return success;
875
876 }
877
878 private void createNewIndexFile() throws IOException {
879 if (indexFile.exists()) {
880 indexFile.delete();
881 if (LOG.isDebugEnabled()) {
882 LOG.debug("Index file " + indexFile + " deleted.");
883 }
884 }
885 if (indexFile.createNewFile()) {
886 if (LOG.isDebugEnabled()) {
887 LOG.debug("Index file " + indexFile + " created successfully");
888 }
889 } else {
890 throw new IOException("Index file " + indexFile + " could not created.");
891 }
892 }
893
894
895
896
897
898
899
900
901
902 public void expireElements() {
903 final long now = System.currentTimeMillis();
904
905
906 synchronized (spoolLock) {
907 for (Iterator iterator = spool.values().iterator(); iterator.hasNext();) {
908 final Element element = (Element) iterator.next();
909 if (element.isExpired()) {
910
911 if (LOG.isDebugEnabled()) {
912 LOG.debug(name + "Cache: Removing expired spool element " + element.getObjectKey());
913 }
914 iterator.remove();
915 notifyExpiryListeners(element);
916 }
917 }
918 }
919
920 Element element = null;
921 RegisteredEventListeners listeners = cache.getCacheEventNotificationService();
922 synchronized (diskElements) {
923
924 for (Iterator iterator = diskElements.entrySet().iterator(); iterator.hasNext();) {
925 final Map.Entry entry = (Map.Entry) iterator.next();
926 final DiskElement diskElement = (DiskElement) entry.getValue();
927
928 if (now >= diskElement.expiryTime) {
929
930 if (LOG.isDebugEnabled()) {
931 LOG.debug(name + "Cache: Removing expired spool element " + entry.getKey() + " from Disk Store");
932 }
933
934 iterator.remove();
935
936
937 if (listeners.hasCacheEventListeners()) {
938 try {
939 element = loadElementFromDiskElement(diskElement);
940 notifyExpiryListeners(element);
941 } catch (Exception exception) {
942 LOG.error(name + "Cache: Could not remove disk store entry for " + entry.getKey()
943 + ". Error was " + exception.getMessage(), exception);
944 }
945 }
946 freeBlock(diskElement);
947 }
948 }
949 }
950 }
951
952
953
954
955
956
957
958 private void notifyExpiryListeners(Element element) {
959 cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
960 }
961
962
963
964
965 private DiskElement findFreeBlock(final int length) {
966 for (int i = 0; i < freeSpace.size(); i++) {
967 final DiskElement element = (DiskElement) freeSpace.get(i);
968 if (element.blockSize >= length) {
969 freeSpace.remove(i);
970 return element;
971 }
972 }
973 return null;
974 }
975
976
977
978
979 public final String toString() {
980 StringBuffer sb = new StringBuffer();
981 sb.append("[ dataFile = ").append(dataFile.getAbsolutePath())
982 .append(", active=").append(active)
983 .append(", totalSize=").append(totalSize)
984 .append(", status=").append(status)
985 .append(", expiryThreadInterval = ").append(expiryThreadInterval)
986 .append(" ]");
987 return sb.toString();
988 }
989
990
991
992
993
994
995
996 public static String generateUniqueDirectory() {
997 return DiskStore.AUTO_DISK_PATH_DIRECTORY_PREFIX + "_" + System.currentTimeMillis();
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009 private static final class DiskElement implements Serializable, LfuPolicy.Metadata {
1010
1011 private static final long serialVersionUID = -717310932566592289L;
1012
1013
1014
1015
1016 private long position;
1017
1018
1019
1020
1021 private int payloadSize;
1022
1023
1024
1025
1026 private int blockSize;
1027
1028
1029
1030
1031
1032
1033 private Object key;
1034
1035
1036
1037
1038 private long expiryTime;
1039
1040
1041
1042
1043 private long hitcount;
1044
1045
1046
1047
1048
1049 public Object getKey() {
1050 return key;
1051 }
1052
1053
1054
1055
1056 public long getHitCount() {
1057 return hitcount;
1058 }
1059 }
1060
1061
1062
1063
1064 private final class SpoolAndExpiryThread extends Thread {
1065 public SpoolAndExpiryThread() {
1066 super("Store " + name + " Spool Thread");
1067 setDaemon(true);
1068 setPriority(Thread.NORM_PRIORITY);
1069 }
1070
1071
1072
1073
1074 public final void run() {
1075 spoolAndExpiryThreadMain();
1076 }
1077 }
1078
1079
1080
1081
1082 public final long getTotalFileSize() {
1083 return getDataFileSize() + getIndexFileSize();
1084 }
1085
1086
1087
1088
1089 public final long getDataFileSize() {
1090 return dataFile.length();
1091 }
1092
1093
1094
1095
1096
1097
1098
1099 public final float calculateDataFileSparseness() {
1100 return 1 - ((float) getUsedDataSize() / (float) getDataFileSize());
1101 }
1102
1103
1104
1105
1106
1107
1108
1109
1110 public final long getUsedDataSize() {
1111 return totalSize;
1112 }
1113
1114
1115
1116
1117 public final long getIndexFileSize() {
1118 if (indexFile == null) {
1119 return 0;
1120 } else {
1121 return indexFile.length();
1122 }
1123 }
1124
1125
1126
1127
1128 public final String getDataFileName() {
1129 return name + ".data";
1130 }
1131
1132
1133
1134
1135 public final String getDataFilePath() {
1136 return diskPath;
1137 }
1138
1139
1140
1141
1142
1143 public final String getIndexFileName() {
1144 return name + ".index";
1145 }
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155 public final boolean isSpoolThreadAlive() {
1156 if (spoolAndExpiryThread == null) {
1157 return false;
1158 } else {
1159 return spoolAndExpiryThread.isAlive();
1160 }
1161 }
1162
1163 private void evictLfuDiskElement() {
1164 synchronized (diskElements) {
1165 DiskElement diskElement = findRelativelyUnused();
1166 diskElements.remove(diskElement.key);
1167 notifyEvictionListeners(diskElement);
1168 freeBlock(diskElement);
1169 }
1170 }
1171
1172
1173
1174
1175 private DiskElement findRelativelyUnused() {
1176 LfuPolicy.Metadata[] elements = sampleElements(diskElements);
1177 LfuPolicy.Metadata metadata = LfuPolicy.leastHit(elements, null);
1178 return (DiskElement) metadata;
1179 }
1180
1181
1182
1183
1184
1185
1186 private LfuPolicy.Metadata[] sampleElements(Map map) {
1187 int[] offsets = LfuPolicy.generateRandomSample(map.size());
1188 DiskElement[] elements = new DiskElement[offsets.length];
1189 Iterator iterator = map.values().iterator();
1190 for (int i = 0; i < offsets.length; i++) {
1191 for (int j = 0; j < offsets[i]; j++) {
1192 iterator.next();
1193 }
1194 elements[i] = (DiskElement) iterator.next();
1195 }
1196 return elements;
1197 }
1198
1199 private void notifyEvictionListeners(DiskElement diskElement) {
1200 RegisteredEventListeners listeners = cache.getCacheEventNotificationService();
1201
1202 if (listeners.hasCacheEventListeners()) {
1203 Element element = null;
1204 try {
1205 element = loadElementFromDiskElement(diskElement);
1206 cache.getCacheEventNotificationService().notifyElementEvicted(element, false);
1207 } catch (Exception exception) {
1208 LOG.error(name + "Cache: Could not notify disk store eviction of " + element.getObjectKey() +
1209 ". Error was " + exception.getMessage(), exception);
1210 }
1211 }
1212
1213 }
1214
1215
1216 }