1 package org.apache.commons.jcs3.auxiliary.disk.indexed;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.Serializable;
25 import java.nio.file.Files;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.concurrent.ConcurrentSkipListSet;
36 import java.util.concurrent.atomic.AtomicInteger;
37 import java.util.concurrent.atomic.AtomicLong;
38 import java.util.concurrent.locks.ReentrantReadWriteLock;
39
40 import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
41 import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache;
42 import org.apache.commons.jcs3.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
43 import org.apache.commons.jcs3.engine.behavior.ICacheElement;
44 import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
45 import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
46 import org.apache.commons.jcs3.engine.control.group.GroupId;
47 import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
48 import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
49 import org.apache.commons.jcs3.engine.stats.StatElement;
50 import org.apache.commons.jcs3.engine.stats.Stats;
51 import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
52 import org.apache.commons.jcs3.engine.stats.behavior.IStats;
53 import org.apache.commons.jcs3.log.Log;
54 import org.apache.commons.jcs3.log.LogManager;
55 import org.apache.commons.jcs3.utils.struct.AbstractLRUMap;
56 import org.apache.commons.jcs3.utils.struct.LRUMap;
57 import org.apache.commons.jcs3.utils.timing.ElapsedTimer;
58
59
60
61
62
63
64 public class IndexedDiskCache<K, V> extends AbstractDiskCache<K, V>
65 {
66
67 private static final Log log = LogManager.getLog(IndexedDiskCache.class);
68
69
70 protected final String logCacheName;
71
72
73 private final String fileName;
74
75
76 private IndexedDisk dataFile;
77
78
79 private IndexedDisk keyFile;
80
81
82 private final Map<K, IndexedDiskElementDescriptor> keyHash;
83
84
85 private final int maxKeySize;
86
87
88 private File rafDir;
89
90
91 private boolean doRecycle = true;
92
93
94 private boolean isRealTimeOptimizationEnabled = true;
95
96
97 private boolean isShutdownOptimizationEnabled = true;
98
99
100 private boolean isOptimizing;
101
102
103 private int timesOptimized;
104
105
106 private volatile Thread currentOptimizationThread;
107
108
109 private int removeCount;
110
111
112 private boolean queueInput;
113
114
115 private final ConcurrentSkipListSet<IndexedDiskElementDescriptor> queuedPutList;
116
117
118 private final ConcurrentSkipListSet<IndexedDiskElementDescriptor> recycle;
119
120
121 private final IndexedDiskCacheAttributes cattr;
122
123
124 private int recycleCnt;
125
126
127 private int startupSize;
128
129
130 private final AtomicLong bytesFree = new AtomicLong();
131
132
133 private DiskLimitType diskLimitType = DiskLimitType.COUNT;
134
135
136 private final AtomicInteger hitCount = new AtomicInteger(0);
137
138
139
140
141 protected ReentrantReadWriteLock storageLock = new ReentrantReadWriteLock();
142
143
144
145
146
147
148
149 public IndexedDiskCache(final IndexedDiskCacheAttributes cacheAttributes)
150 {
151 this(cacheAttributes, null);
152 }
153
154
155
156
157
158
159
160
161
162 public IndexedDiskCache(final IndexedDiskCacheAttributes cattr, final IElementSerializer elementSerializer)
163 {
164 super(cattr);
165
166 setElementSerializer(elementSerializer);
167
168 this.cattr = cattr;
169 this.maxKeySize = cattr.getMaxKeySize();
170 this.isRealTimeOptimizationEnabled = cattr.getOptimizeAtRemoveCount() > 0;
171 this.isShutdownOptimizationEnabled = cattr.isOptimizeOnShutdown();
172 this.logCacheName = "Region [" + getCacheName() + "] ";
173 this.diskLimitType = cattr.getDiskLimitType();
174
175 this.fileName = getCacheName().replaceAll("[^a-zA-Z0-9-_\\.]", "_");
176 this.keyHash = createInitialKeyMap();
177 this.queuedPutList = new ConcurrentSkipListSet<>(new PositionComparator());
178 this.recycle = new ConcurrentSkipListSet<>();
179
180 try
181 {
182 initializeFileSystem(cattr);
183 initializeKeysAndData(cattr);
184
185
186 setAlive(true);
187 log.info("{0}: Indexed Disk Cache is alive.", logCacheName);
188
189
190 if (isRealTimeOptimizationEnabled && !keyHash.isEmpty())
191 {
192
193 doOptimizeRealTime();
194 }
195 }
196 catch (final IOException e)
197 {
198 log.error("{0}: Failure initializing for fileName: {1} and directory: {2}",
199 logCacheName, fileName, this.rafDir.getAbsolutePath(), e);
200 }
201 }
202
203
204
205
206
207
208
209 private void initializeFileSystem(final IndexedDiskCacheAttributes cattr)
210 {
211 this.rafDir = cattr.getDiskPath();
212 log.info("{0}: Cache file root directory: {1}", logCacheName, rafDir);
213 }
214
215
216
217
218
219
220
221
222
223
224 private void initializeKeysAndData(final IndexedDiskCacheAttributes cattr) throws IOException
225 {
226 this.dataFile = new IndexedDisk(new File(rafDir, fileName + ".data"), getElementSerializer());
227 this.keyFile = new IndexedDisk(new File(rafDir, fileName + ".key"), getElementSerializer());
228
229 if (cattr.isClearDiskOnStartup())
230 {
231 log.info("{0}: ClearDiskOnStartup is set to true. Ignoring any persisted data.",
232 logCacheName);
233 initializeEmptyStore();
234 }
235 else if (!keyFile.isEmpty())
236 {
237
238
239 initializeStoreFromPersistedData();
240 }
241 else
242 {
243
244
245 initializeEmptyStore();
246 }
247 }
248
249
250
251
252
253
254
255 private void initializeEmptyStore() throws IOException
256 {
257 this.keyHash.clear();
258
259 if (!dataFile.isEmpty())
260 {
261 dataFile.reset();
262 }
263 }
264
265
266
267
268
269
270
271
272 private void initializeStoreFromPersistedData() throws IOException
273 {
274 loadKeys();
275
276 if (keyHash.isEmpty())
277 {
278 dataFile.reset();
279 }
280 else
281 {
282 final boolean isOk = checkKeyDataConsistency(false);
283 if (!isOk)
284 {
285 keyHash.clear();
286 keyFile.reset();
287 dataFile.reset();
288 log.warn("{0}: Corruption detected. Resetting data and keys files.", logCacheName);
289 }
290 else
291 {
292 synchronized (this)
293 {
294 startupSize = keyHash.size();
295 }
296 }
297 }
298 }
299
300
301
302
303
304 protected void loadKeys()
305 {
306 log.debug("{0}: Loading keys for {1}", () -> logCacheName, () -> keyFile.toString());
307
308 storageLock.writeLock().lock();
309
310 try
311 {
312
313 keyHash.clear();
314
315 final HashMap<K, IndexedDiskElementDescriptor> keys = keyFile.readObject(
316 new IndexedDiskElementDescriptor(0, (int) keyFile.length() - IndexedDisk.HEADER_SIZE_BYTES));
317
318 if (keys != null)
319 {
320 log.debug("{0}: Found {1} in keys file.", logCacheName, keys.size());
321
322 keyHash.putAll(keys);
323
324 log.info("{0}: Loaded keys from [{1}], key count: {2}; up to {3} will be available.",
325 () -> logCacheName, () -> fileName, keyHash::size, () -> maxKeySize);
326 }
327
328 if (log.isTraceEnabled())
329 {
330 dump(false);
331 }
332 }
333 catch (final Exception e)
334 {
335 log.error("{0}: Problem loading keys for file {1}", logCacheName, fileName, e);
336 }
337 finally
338 {
339 storageLock.writeLock().unlock();
340 }
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354 private boolean checkKeyDataConsistency(final boolean checkForDedOverlaps)
355 {
356 final ElapsedTimer timer = new ElapsedTimer();
357 log.debug("{0}: Performing inital consistency check", logCacheName);
358
359 boolean isOk = true;
360 try
361 {
362 final long fileLength = dataFile.length();
363
364 final IndexedDiskElementDescriptor corruptDed = keyHash.values().stream()
365 .filter(ded -> ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len > fileLength)
366 .findFirst()
367 .orElse(null);
368
369 if (corruptDed != null)
370 {
371 isOk = false;
372 log.warn("{0}: The dataFile is corrupted!\n raf.length() = {1}\n ded.pos = {2}",
373 logCacheName, fileLength, corruptDed.pos);
374 }
375 else if (checkForDedOverlaps)
376 {
377 isOk = checkForDedOverlaps(createPositionSortedDescriptorList());
378 }
379 }
380 catch (final IOException e)
381 {
382 log.error(e);
383 isOk = false;
384 }
385
386 log.info("{0}: Finished inital consistency check, isOk = {1} in {2}",
387 logCacheName, isOk, timer.getElapsedTimeString());
388
389 return isOk;
390 }
391
392
393
394
395
396
397
398
399
400
401 protected boolean checkForDedOverlaps(final IndexedDiskElementDescriptor[] sortedDescriptors)
402 {
403 final ElapsedTimer timer = new ElapsedTimer();
404 boolean isOk = true;
405 long expectedNextPos = 0;
406 for (final IndexedDiskElementDescriptor ded : sortedDescriptors) {
407 if (expectedNextPos > ded.pos)
408 {
409 log.error("{0}: Corrupt file: overlapping deds {1}", logCacheName, ded);
410 isOk = false;
411 break;
412 }
413 expectedNextPos = ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len;
414 }
415 log.debug("{0}: Check for DED overlaps took {1} ms.", () -> logCacheName,
416 timer::getElapsedTime);
417
418 return isOk;
419 }
420
421
422
423
424 protected void saveKeys()
425 {
426 try
427 {
428 log.info("{0}: Saving keys to: {1}, key count: {2}",
429 () -> logCacheName, () -> fileName, keyHash::size);
430
431 keyFile.reset();
432
433 final HashMap<K, IndexedDiskElementDescriptor> keys = new HashMap<>(keyHash);
434 if (!keys.isEmpty())
435 {
436 keyFile.writeObject(keys, 0);
437 }
438
439 log.info("{0}: Finished saving keys.", logCacheName);
440 }
441 catch (final IOException e)
442 {
443 log.error("{0}: Problem storing keys.", logCacheName, e);
444 }
445 }
446
447
448
449
450
451
452
453
454
455 @Override
456 protected void processUpdate(final ICacheElement<K, V> ce)
457 {
458 if (!isAlive())
459 {
460 log.error("{0}: No longer alive; aborting put of key = {1}",
461 () -> logCacheName, ce::getKey);
462 return;
463 }
464
465 log.debug("{0}: Storing element on disk, key: {1}",
466 () -> logCacheName, ce::getKey);
467
468
469 IndexedDiskElementDescriptor old = null;
470
471 try
472 {
473 IndexedDiskElementDescriptor ded = null;
474 final byte[] data = getElementSerializer().serialize(ce);
475
476
477 storageLock.writeLock().lock();
478 try
479 {
480 old = keyHash.get(ce.getKey());
481
482
483
484 if (old != null && data.length <= old.len)
485 {
486
487
488 ded = old;
489 ded.len = data.length;
490 }
491 else
492 {
493
494 ded = new IndexedDiskElementDescriptor(dataFile.length(), data.length);
495
496 if (doRecycle)
497 {
498 final IndexedDiskElementDescriptor rep = recycle.ceiling(ded);
499 if (rep != null)
500 {
501
502 recycle.remove(rep);
503 ded = rep;
504 ded.len = data.length;
505 recycleCnt++;
506 this.adjustBytesFree(ded, false);
507 log.debug("{0}: using recycled ded {1} rep.len = {2} ded.len = {3}",
508 logCacheName, ded.pos, rep.len, ded.len);
509 }
510 }
511
512
513 keyHash.put(ce.getKey(), ded);
514
515 if (queueInput)
516 {
517 queuedPutList.add(ded);
518 log.debug("{0}: added to queued put list. {1}",
519 () -> logCacheName, queuedPutList::size);
520 }
521
522
523 if (old != null)
524 {
525 addToRecycleBin(old);
526 }
527 }
528
529 dataFile.write(ded, data);
530 }
531 finally
532 {
533 storageLock.writeLock().unlock();
534 }
535
536 log.debug("{0}: Put to file: {1}, key: {2}, position: {3}, size: {4}",
537 logCacheName, fileName, ce.getKey(), ded.pos, ded.len);
538 }
539 catch (final IOException e)
540 {
541 log.error("{0}: Failure updating element, key: {1} old: {2}",
542 logCacheName, ce.getKey(), old, e);
543 }
544 }
545
546
547
548
549
550
551
552
553
554 @Override
555 protected ICacheElement<K, V> processGet(final K key)
556 {
557 if (!isAlive())
558 {
559 log.error("{0}: No longer alive so returning null for key = {1}",
560 logCacheName, key);
561 return null;
562 }
563
564 log.debug("{0}: Trying to get from disk: {1}", logCacheName, key);
565
566 ICacheElement<K, V> object = null;
567 try
568 {
569 storageLock.readLock().lock();
570 try
571 {
572 object = readElement(key);
573 }
574 finally
575 {
576 storageLock.readLock().unlock();
577 }
578
579 if (object != null)
580 {
581 hitCount.incrementAndGet();
582 }
583 }
584 catch (final IOException ioe)
585 {
586 log.error("{0}: Failure getting from disk, key = {1}", logCacheName, key, ioe);
587 reset();
588 }
589 return object;
590 }
591
592
593
594
595
596
597
598
599
600 @Override
601 public Map<K, ICacheElement<K, V>> processGetMatching(final String pattern)
602 {
603 final Map<K, ICacheElement<K, V>> elements = new HashMap<>();
604 Set<K> keyArray = null;
605 storageLock.readLock().lock();
606 try
607 {
608 keyArray = new HashSet<>(keyHash.keySet());
609 }
610 finally
611 {
612 storageLock.readLock().unlock();
613 }
614
615 final Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray(pattern, keyArray);
616
617 for (final K key : matchingKeys)
618 {
619 final ICacheElement<K, V> element = processGet(key);
620 if (element != null)
621 {
622 elements.put(key, element);
623 }
624 }
625 return elements;
626 }
627
628
629
630
631
632
633
634
635
636 private ICacheElement<K, V> readElement(final K key) throws IOException
637 {
638 final IndexedDiskElementDescriptor ded = keyHash.get(key);
639
640 if (ded != null)
641 {
642 log.debug("{0}: Found on disk, key: ", logCacheName, key);
643
644 try
645 {
646 return dataFile.readObject(ded);
647
648 }
649 catch (final IOException e)
650 {
651 log.error("{0}: IO Exception, Problem reading object from file", logCacheName, e);
652 throw e;
653 }
654 catch (final Exception e)
655 {
656 log.error("{0}: Exception, Problem reading object from file", logCacheName, e);
657 throw new IOException(logCacheName + "Problem reading object from disk.", e);
658 }
659 }
660
661 return null;
662 }
663
664
665
666
667
668
669
670 @Override
671 public Set<K> getKeySet() throws IOException
672 {
673 final HashSet<K> keys = new HashSet<>();
674
675 storageLock.readLock().lock();
676
677 try
678 {
679 keys.addAll(this.keyHash.keySet());
680 }
681 finally
682 {
683 storageLock.readLock().unlock();
684 }
685
686 return keys;
687 }
688
689
690
691
692
693
694
695
696
697 @Override
698 protected boolean processRemove(final K key)
699 {
700 if (!isAlive())
701 {
702 log.error("{0}: No longer alive so returning false for key = {1}", logCacheName, key);
703 return false;
704 }
705
706 if (key == null)
707 {
708 return false;
709 }
710
711 boolean removed = false;
712 try
713 {
714 storageLock.writeLock().lock();
715
716 if (key instanceof String && key.toString().endsWith(NAME_COMPONENT_DELIMITER))
717 {
718 removed = performPartialKeyRemoval((String) key);
719 }
720 else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
721 {
722 removed = performGroupRemoval(((GroupAttrName<?>) key).groupId);
723 }
724 else
725 {
726 removed = performSingleKeyRemoval(key);
727 }
728 }
729 finally
730 {
731 storageLock.writeLock().unlock();
732 }
733
734
735
736 if (removed)
737 {
738 doOptimizeRealTime();
739 }
740
741 return removed;
742 }
743
744
745
746
747
748
749
750
751
752
753
754 private boolean performPartialKeyRemoval(final String key)
755 {
756 boolean removed = false;
757
758
759 final List<K> itemsToRemove = new LinkedList<>();
760
761 for (final K k : keyHash.keySet())
762 {
763 if (k instanceof String && k.toString().startsWith(key))
764 {
765 itemsToRemove.add(k);
766 }
767 }
768
769
770 for (final K fullKey : itemsToRemove)
771 {
772
773
774 performSingleKeyRemoval(fullKey);
775 removed = true;
776
777 }
778
779 return removed;
780 }
781
782
783
784
785
786
787
788
789
790
791
792 private boolean performGroupRemoval(final GroupId key)
793 {
794 boolean removed = false;
795
796
797 final List<K> itemsToRemove = new LinkedList<>();
798
799
800 for (final K k : keyHash.keySet())
801 {
802 if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(key))
803 {
804 itemsToRemove.add(k);
805 }
806 }
807
808
809 for (final K fullKey : itemsToRemove)
810 {
811
812
813 performSingleKeyRemoval(fullKey);
814 removed = true;
815
816 }
817
818 return removed;
819 }
820
821
822
823
824
825
826
827
828
829
830 private boolean performSingleKeyRemoval(final K key)
831 {
832 final boolean removed;
833
834 final IndexedDiskElementDescriptor ded = keyHash.remove(key);
835 removed = ded != null;
836 addToRecycleBin(ded);
837
838 log.debug("{0}: Disk removal: Removed from key hash, key [{1}] removed = {2}",
839 logCacheName, key, removed);
840 return removed;
841 }
842
843
844
845
846 @Override
847 public void processRemoveAll()
848 {
849 final ICacheEvent<String> cacheEvent =
850 createICacheEvent(getCacheName(), "all", ICacheEventLogger.REMOVEALL_EVENT);
851 try
852 {
853 reset();
854 }
855 finally
856 {
857 logICacheEvent(cacheEvent);
858 }
859 }
860
861
862
863
864
865
866 private void reset()
867 {
868 log.info("{0}: Resetting cache", logCacheName);
869
870 try
871 {
872 storageLock.writeLock().lock();
873
874 if (dataFile != null)
875 {
876 dataFile.close();
877 }
878
879 final File dataFileTemp = new File(rafDir, fileName + ".data");
880 Files.delete(dataFileTemp.toPath());
881
882 if (keyFile != null)
883 {
884 keyFile.close();
885 }
886 final File keyFileTemp = new File(rafDir, fileName + ".key");
887 Files.delete(keyFileTemp.toPath());
888
889 dataFile = new IndexedDisk(dataFileTemp, getElementSerializer());
890 keyFile = new IndexedDisk(keyFileTemp, getElementSerializer());
891
892 this.recycle.clear();
893 this.keyHash.clear();
894 }
895 catch (final IOException e)
896 {
897 log.error("{0}: Failure resetting state", logCacheName, e);
898 }
899 finally
900 {
901 storageLock.writeLock().unlock();
902 }
903 }
904
905
906
907
908
909
910 private Map<K, IndexedDiskElementDescriptor> createInitialKeyMap()
911 {
912 Map<K, IndexedDiskElementDescriptor> keyMap = null;
913 if (maxKeySize >= 0)
914 {
915 if (this.diskLimitType == DiskLimitType.COUNT)
916 {
917 keyMap = new LRUMapCountLimited(maxKeySize);
918 }
919 else
920 {
921 keyMap = new LRUMapSizeLimited(maxKeySize);
922 }
923
924 log.info("{0}: Set maxKeySize to: \"{1}\"", logCacheName, maxKeySize);
925 }
926 else
927 {
928
929 keyMap = new HashMap<>();
930
931 log.info("{0}: Set maxKeySize to unlimited", logCacheName);
932 }
933
934 return keyMap;
935 }
936
937
938
939
940
941
942
943 @Override
944 public void processDispose()
945 {
946 final ICacheEvent<String> cacheEvent = createICacheEvent(getCacheName(), "none", ICacheEventLogger.DISPOSE_EVENT);
947 try
948 {
949 final Thread t = new Thread(this::disposeInternal, "IndexedDiskCache-DisposalThread");
950 t.start();
951
952 try
953 {
954 t.join(60 * 1000);
955 }
956 catch (final InterruptedException ex)
957 {
958 log.error("{0}: Interrupted while waiting for disposal thread to finish.",
959 logCacheName, ex);
960 }
961 }
962 finally
963 {
964 logICacheEvent(cacheEvent);
965 }
966 }
967
968
969
970
971 protected void disposeInternal()
972 {
973 if (!isAlive())
974 {
975 log.error("{0}: Not alive and dispose was called, filename: {1}",
976 logCacheName, fileName);
977 return;
978 }
979
980
981 setAlive(false);
982
983 final Thread optimizationThread = currentOptimizationThread;
984 if (isRealTimeOptimizationEnabled && optimizationThread != null)
985 {
986
987 log.debug("{0}: In dispose, optimization already in progress; waiting for completion.",
988 logCacheName);
989
990 try
991 {
992 optimizationThread.join();
993 }
994 catch (final InterruptedException e)
995 {
996 log.error("{0}: Unable to join current optimization thread.",
997 logCacheName, e);
998 }
999 }
1000 else if (isShutdownOptimizationEnabled && this.getBytesFree() > 0)
1001 {
1002 optimizeFile();
1003 }
1004
1005 saveKeys();
1006
1007 try
1008 {
1009 log.debug("{0}: Closing files, base filename: {1}", logCacheName,
1010 fileName);
1011 dataFile.close();
1012 dataFile = null;
1013 keyFile.close();
1014 keyFile = null;
1015 }
1016 catch (final IOException e)
1017 {
1018 log.error("{0}: Failure closing files in dispose, filename: {1}",
1019 logCacheName, fileName, e);
1020 }
1021
1022 log.info("{0}: Shutdown complete.", logCacheName);
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036 protected void addToRecycleBin(final IndexedDiskElementDescriptor ded)
1037 {
1038
1039 if (ded != null)
1040 {
1041 storageLock.readLock().lock();
1042
1043 try
1044 {
1045 adjustBytesFree(ded, true);
1046
1047 if (doRecycle)
1048 {
1049 recycle.add(ded);
1050 log.debug("{0}: recycled ded {1}", logCacheName, ded);
1051 }
1052 }
1053 finally
1054 {
1055 storageLock.readLock().unlock();
1056 }
1057 }
1058 }
1059
1060
1061
1062
1063 protected void doOptimizeRealTime()
1064 {
1065 if (isRealTimeOptimizationEnabled && !isOptimizing
1066 && removeCount++ >= cattr.getOptimizeAtRemoveCount())
1067 {
1068 isOptimizing = true;
1069
1070 log.info("{0}: Optimizing file. removeCount [{1}] OptimizeAtRemoveCount [{2}]",
1071 logCacheName, removeCount, cattr.getOptimizeAtRemoveCount());
1072
1073 if (currentOptimizationThread == null)
1074 {
1075 storageLock.writeLock().lock();
1076
1077 try
1078 {
1079 if (currentOptimizationThread == null)
1080 {
1081 currentOptimizationThread = new Thread(() -> {
1082 optimizeFile();
1083 currentOptimizationThread = null;
1084 }, "IndexedDiskCache-OptimizationThread");
1085 }
1086 }
1087 finally
1088 {
1089 storageLock.writeLock().unlock();
1090 }
1091
1092 if (currentOptimizationThread != null)
1093 {
1094 currentOptimizationThread.start();
1095 }
1096 }
1097 }
1098 }
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120 protected void optimizeFile()
1121 {
1122 final ElapsedTimer timer = new ElapsedTimer();
1123 timesOptimized++;
1124 log.info("{0}: Beginning Optimization #{1}", logCacheName, timesOptimized);
1125
1126
1127 IndexedDiskElementDescriptor[] defragList = null;
1128
1129 storageLock.writeLock().lock();
1130
1131 try
1132 {
1133 queueInput = true;
1134
1135 doRecycle = false;
1136 defragList = createPositionSortedDescriptorList();
1137 }
1138 finally
1139 {
1140 storageLock.writeLock().unlock();
1141 }
1142
1143
1144
1145 long expectedNextPos = defragFile(defragList, 0);
1146
1147
1148 storageLock.writeLock().lock();
1149
1150 try
1151 {
1152 try
1153 {
1154 if (!queuedPutList.isEmpty())
1155 {
1156 defragList = queuedPutList.toArray(new IndexedDiskElementDescriptor[0]);
1157
1158
1159 expectedNextPos = defragFile(defragList, expectedNextPos);
1160 }
1161
1162 dataFile.truncate(expectedNextPos);
1163 }
1164 catch (final IOException e)
1165 {
1166 log.error("{0}: Error optimizing queued puts.", logCacheName, e);
1167 }
1168
1169
1170 removeCount = 0;
1171 resetBytesFree();
1172 this.recycle.clear();
1173 queuedPutList.clear();
1174 queueInput = false;
1175
1176 doRecycle = true;
1177 isOptimizing = false;
1178 }
1179 finally
1180 {
1181 storageLock.writeLock().unlock();
1182 }
1183
1184 log.info("{0}: Finished #{1}, Optimization took {2}",
1185 logCacheName, timesOptimized, timer.getElapsedTimeString());
1186 }
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200 private long defragFile(final IndexedDiskElementDescriptor[] defragList, final long startingPos)
1201 {
1202 final ElapsedTimer timer = new ElapsedTimer();
1203 long preFileSize = 0;
1204 long postFileSize = 0;
1205 long expectedNextPos = 0;
1206 try
1207 {
1208 preFileSize = this.dataFile.length();
1209
1210 expectedNextPos = startingPos;
1211 for (final IndexedDiskElementDescriptor element : defragList) {
1212 storageLock.writeLock().lock();
1213 try
1214 {
1215 if (expectedNextPos != element.pos)
1216 {
1217 dataFile.move(element, expectedNextPos);
1218 }
1219 expectedNextPos = element.pos + IndexedDisk.HEADER_SIZE_BYTES + element.len;
1220 }
1221 finally
1222 {
1223 storageLock.writeLock().unlock();
1224 }
1225 }
1226
1227 postFileSize = this.dataFile.length();
1228
1229
1230 return expectedNextPos;
1231 }
1232 catch (final IOException e)
1233 {
1234 log.error("{0}: Error occurred during defragmentation.", logCacheName, e);
1235 }
1236 finally
1237 {
1238 log.info("{0}: Defragmentation took {1}. File Size (before={2}) (after={3}) (truncating to {4})",
1239 logCacheName, timer.getElapsedTimeString(), preFileSize, postFileSize, expectedNextPos);
1240 }
1241
1242 return 0;
1243 }
1244
1245
1246
1247
1248
1249
1250
1251
1252 private IndexedDiskElementDescriptor[] createPositionSortedDescriptorList()
1253 {
1254 final List<IndexedDiskElementDescriptor> defragList = new ArrayList<>(keyHash.values());
1255 Collections.sort(defragList, (ded1, ded2) -> Long.compare(ded1.pos, ded2.pos));
1256
1257 return defragList.toArray(new IndexedDiskElementDescriptor[0]);
1258 }
1259
1260
1261
1262
1263
1264
1265
1266 @Override
1267 public int getSize()
1268 {
1269 return keyHash.size();
1270 }
1271
1272
1273
1274
1275
1276
1277
1278 protected int getRecyleBinSize()
1279 {
1280 return this.recycle.size();
1281 }
1282
1283
1284
1285
1286
1287
1288
1289 protected int getRecyleCount()
1290 {
1291 return this.recycleCnt;
1292 }
1293
1294
1295
1296
1297
1298
1299
1300
1301 protected long getBytesFree()
1302 {
1303 return this.bytesFree.get();
1304 }
1305
1306
1307
1308
1309 private void resetBytesFree()
1310 {
1311 this.bytesFree.set(0);
1312 }
1313
1314
1315
1316
1317
1318
1319
1320
1321 private void adjustBytesFree(final IndexedDiskElementDescriptor ded, final boolean add)
1322 {
1323 if (ded != null)
1324 {
1325 final int amount = ded.len + IndexedDisk.HEADER_SIZE_BYTES;
1326
1327 if (add)
1328 {
1329 this.bytesFree.addAndGet(amount);
1330 }
1331 else
1332 {
1333 this.bytesFree.addAndGet(-amount);
1334 }
1335 }
1336 }
1337
1338
1339
1340
1341
1342
1343
1344
1345 protected long getDataFileSize() throws IOException
1346 {
1347 long size = 0;
1348
1349 storageLock.readLock().lock();
1350
1351 try
1352 {
1353 if (dataFile != null)
1354 {
1355 size = dataFile.length();
1356 }
1357 }
1358 finally
1359 {
1360 storageLock.readLock().unlock();
1361 }
1362
1363 return size;
1364 }
1365
1366
1367
1368
1369 public void dump()
1370 {
1371 dump(true);
1372 }
1373
1374
1375
1376
1377
1378
1379
1380
1381 public void dump(final boolean dumpValues)
1382 {
1383 if (log.isTraceEnabled())
1384 {
1385 log.trace("{0}: [dump] Number of keys: {1}", logCacheName, keyHash.size());
1386
1387 for (final Map.Entry<K, IndexedDiskElementDescriptor> e : keyHash.entrySet())
1388 {
1389 final K key = e.getKey();
1390 final IndexedDiskElementDescriptor ded = e.getValue();
1391
1392 log.trace("{0}: [dump] Disk element, key: {1}, pos: {2}, len: {3}" +
1393 (dumpValues ? ", val: " + get(key) : ""),
1394 logCacheName, key, ded.pos, ded.len);
1395 }
1396 }
1397 }
1398
1399
1400
1401
1402 @Override
1403 public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
1404 {
1405 return this.cattr;
1406 }
1407
1408
1409
1410
1411
1412
1413
1414 @Override
1415 public synchronized IStats getStatistics()
1416 {
1417 final IStats stats = new Stats();
1418 stats.setTypeName("Indexed Disk Cache");
1419
1420 final ArrayList<IStatElement<?>> elems = new ArrayList<>();
1421
1422 elems.add(new StatElement<>("Is Alive", Boolean.valueOf(isAlive())));
1423 elems.add(new StatElement<>("Key Map Size", Integer.valueOf(this.keyHash != null ? this.keyHash.size() : -1)));
1424 try
1425 {
1426 elems.add(
1427 new StatElement<>("Data File Length", Long.valueOf(this.dataFile != null ? this.dataFile.length() : -1L)));
1428 }
1429 catch (final IOException e)
1430 {
1431 log.error(e);
1432 }
1433 elems.add(new StatElement<>("Max Key Size", this.maxKeySize));
1434 elems.add(new StatElement<>("Hit Count", this.hitCount));
1435 elems.add(new StatElement<>("Bytes Free", this.bytesFree));
1436 elems.add(new StatElement<>("Optimize Operation Count", Integer.valueOf(this.removeCount)));
1437 elems.add(new StatElement<>("Times Optimized", Integer.valueOf(this.timesOptimized)));
1438 elems.add(new StatElement<>("Recycle Count", Integer.valueOf(this.recycleCnt)));
1439 elems.add(new StatElement<>("Recycle Bin Size", Integer.valueOf(this.recycle.size())));
1440 elems.add(new StatElement<>("Startup Size", Integer.valueOf(this.startupSize)));
1441
1442
1443 final IStats sStats = super.getStatistics();
1444 elems.addAll(sStats.getStatElements());
1445
1446 stats.setStatElements(elems);
1447
1448 return stats;
1449 }
1450
1451
1452
1453
1454
1455
1456
1457 protected int getTimesOptimized()
1458 {
1459 return timesOptimized;
1460 }
1461
1462
1463
1464
1465
1466
1467
1468 @Override
1469 protected String getDiskLocation()
1470 {
1471 return dataFile.getFilePath();
1472 }
1473
1474
1475
1476
1477
1478
1479 @Deprecated
1480 protected static final class PositionComparator implements Comparator<IndexedDiskElementDescriptor>, Serializable
1481 {
1482
1483 private static final long serialVersionUID = -8387365338590814113L;
1484
1485
1486
1487
1488
1489
1490
1491 @Override
1492 public int compare(final IndexedDiskElementDescriptor ded1, final IndexedDiskElementDescriptor ded2)
1493 {
1494 return Long.compare(ded1.pos, ded2.pos);
1495 }
1496 }
1497
1498
1499
1500
1501
1502 public class LRUMapSizeLimited extends AbstractLRUMap<K, IndexedDiskElementDescriptor>
1503 {
1504
1505
1506
1507 public static final String TAG = "orig";
1508
1509
1510 private final AtomicInteger contentSize;
1511 private final int maxSize;
1512
1513
1514
1515
1516 public LRUMapSizeLimited()
1517 {
1518 this(-1);
1519 }
1520
1521
1522
1523
1524 public LRUMapSizeLimited(final int maxKeySize)
1525 {
1526 this.maxSize = maxKeySize;
1527 this.contentSize = new AtomicInteger(0);
1528 }
1529
1530
1531 private void subLengthFromCacheSize(final IndexedDiskElementDescriptor value)
1532 {
1533 contentSize.addAndGet((value.len + IndexedDisk.HEADER_SIZE_BYTES) / -1024 - 1);
1534 }
1535
1536
1537 private void addLengthToCacheSize(final IndexedDiskElementDescriptor value)
1538 {
1539 contentSize.addAndGet((value.len + IndexedDisk.HEADER_SIZE_BYTES) / 1024 + 1);
1540 }
1541
1542 @Override
1543 public IndexedDiskElementDescriptor put(final K key, final IndexedDiskElementDescriptor value)
1544 {
1545 IndexedDiskElementDescriptor oldValue = null;
1546
1547 try
1548 {
1549 oldValue = super.put(key, value);
1550 }
1551 finally
1552 {
1553
1554 if (value != null)
1555 {
1556 addLengthToCacheSize(value);
1557 }
1558 if (oldValue != null)
1559 {
1560 subLengthFromCacheSize(oldValue);
1561 }
1562 }
1563
1564 return oldValue;
1565 }
1566
1567 @Override
1568 public IndexedDiskElementDescriptor remove(final Object key)
1569 {
1570 IndexedDiskElementDescriptor value = null;
1571
1572 try
1573 {
1574 value = super.remove(key);
1575 return value;
1576 }
1577 finally
1578 {
1579 if (value != null)
1580 {
1581 subLengthFromCacheSize(value);
1582 }
1583 }
1584 }
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594 @Override
1595 protected void processRemovedLRU(final K key, final IndexedDiskElementDescriptor value)
1596 {
1597 if (value != null)
1598 {
1599 subLengthFromCacheSize(value);
1600 }
1601
1602 addToRecycleBin(value);
1603
1604 log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
1605 log.debug("{0}: Key store size: [{1}].", logCacheName, this.size());
1606
1607 doOptimizeRealTime();
1608 }
1609
1610 @Override
1611 protected boolean shouldRemove()
1612 {
1613 return maxSize > 0 && contentSize.get() > maxSize && !this.isEmpty();
1614 }
1615 }
1616
1617
1618
1619
1620
1621
1622 public class LRUMapCountLimited extends LRUMap<K, IndexedDiskElementDescriptor>
1623
1624 {
1625 public LRUMapCountLimited(final int maxKeySize)
1626 {
1627 super(maxKeySize);
1628 }
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638 @Override
1639 protected void processRemovedLRU(final K key, final IndexedDiskElementDescriptor value)
1640 {
1641 addToRecycleBin(value);
1642 log.debug("{0}: Removing key: [{1}] from key store.", logCacheName, key);
1643 log.debug("{0}: Key store size: [{1}].", logCacheName, this.size());
1644
1645 doOptimizeRealTime();
1646 }
1647 }
1648 }