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