1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /***
18 *
19 */
20 package net.sf.ehcache.store.disk;
21
22 import net.sf.ehcache.Element;
23 import net.sf.ehcache.config.CacheConfiguration;
24 import net.sf.ehcache.config.PinningConfiguration;
25 import net.sf.ehcache.pool.PoolAccessor;
26 import net.sf.ehcache.pool.Role;
27 import net.sf.ehcache.store.ElementValueComparator;
28 import net.sf.ehcache.util.ratestatistics.AtomicRateStatistic;
29 import net.sf.ehcache.util.ratestatistics.RateStatistic;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import java.util.Collection;
35 import java.util.Iterator;
36 import java.util.NoSuchElementException;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.locks.ReentrantReadWriteLock;
39
40 /***
41 * Segment implementation used in LocalStore.
42 * <p>
43 * The segment extends ReentrantReadWriteLock to allow read locking on read operations.
44 * In addition to the typical CHM-like methods, this classes additionally supports
45 * replacement under a read lock - which is accomplished using an atomic CAS on the
46 * associated HashEntry.
47 *
48 * @author Chris Dennis
49 * @author Ludovic Orban
50 */
51 public class Segment extends ReentrantReadWriteLock {
52
53 private static final Logger LOG = LoggerFactory.getLogger(Segment.class.getName());
54
55 private static final float LOAD_FACTOR = 0.75f;
56 private static final int MAXIMUM_CAPACITY = Integer.highestOneBit(Integer.MAX_VALUE);
57
58 /***
59 * Count of elements in the map.
60 * <p>
61 * A volatile reference is needed here for the same reasons as in the table reference.
62 */
63 protected volatile int count;
64
65 /***
66 * Mod-count used to track concurrent modifications when doing size calculations or iterating over the store.
67 * <p>
68 * Note that we don't actually have any iterators yet...
69 */
70 protected int modCount;
71
72 /***
73 * The primary substitute factory.
74 * <p>
75 * This is the substitute type used to store <code>Element</code>s when they are first added to the store.
76 */
77 private final DiskStorageFactory disk;
78
79 /***
80 * Table of HashEntry linked lists, indexed by the least-significant bits of the spread-hash value.
81 * <p>
82 * A volatile reference is needed to ensure the visibility of table changes made during rehash operations to size operations.
83 * Key operations are done under read-locks so there is no need for volatility in that regard. Hence if we switched to read-locked
84 * size operations, we wouldn't need a volatile reference here.
85 */
86 private volatile HashEntry[] table;
87
88 /***
89 * Size at which the next rehashing of this Segment should occur
90 */
91 private int threshold;
92
93 private final RateStatistic diskHitRate = new AtomicRateStatistic(1000, TimeUnit.MILLISECONDS);
94 private final RateStatistic diskMissRate = new AtomicRateStatistic(1000, TimeUnit.MILLISECONDS);
95 private final PoolAccessor onHeapPoolAccessor;
96 private final PoolAccessor onDiskPoolAccessor;
97 private volatile boolean cachePinned;
98
99 /***
100 * Create a Segment with the given initial capacity, load-factor, primary element substitute factory, and identity element substitute factory.
101 * <p>
102 * An identity element substitute factory is specified at construction time because only one subclass of IdentityElementProxyFactory
103 * can be used with a Segment. Without this requirement the mapping between bare {@link net.sf.ehcache.Element} instances and the factory
104 * responsible for them would be ambiguous.
105 * <p>
106 * If a <code>null</code> identity element substitute factory is specified then encountering a raw element (i.e. as a result of using an
107 * identity element substitute factory) will result in a null pointer exception during decode.
108 *
109 * @param initialCapacity initial capacity of store
110 * @param loadFactor fraction of capacity at which rehash occurs
111 * @param primary primary element substitute factory
112 * @param cacheConfiguration the cache configuration
113 * @param onHeapPoolAccessor the pool tracking on-heap usage
114 * @param onDiskPoolAccessor the pool tracking on-disk usage
115 */
116 public Segment(int initialCapacity, float loadFactor, DiskStorageFactory primary,
117 CacheConfiguration cacheConfiguration,
118 PoolAccessor onHeapPoolAccessor, PoolAccessor onDiskPoolAccessor) {
119 this.onHeapPoolAccessor = onHeapPoolAccessor;
120 this.onDiskPoolAccessor = onDiskPoolAccessor;
121 this.table = new HashEntry[initialCapacity];
122 this.threshold = (int) (table.length * loadFactor);
123 this.modCount = 0;
124 this.disk = primary;
125 this.cachePinned = determineCachePinned(cacheConfiguration);
126 }
127
128 private boolean determineCachePinned(CacheConfiguration cacheConfiguration) {
129 PinningConfiguration pinningConfiguration = cacheConfiguration.getPinningConfiguration();
130 if (pinningConfiguration == null) {
131 return false;
132 }
133
134 switch (pinningConfiguration.getStore()) {
135 case LOCALHEAP:
136 return false;
137
138 case LOCALMEMORY:
139 return false;
140
141 case INCACHE:
142 return cacheConfiguration.isOverflowToDisk() || cacheConfiguration.isDiskPersistent();
143
144 default:
145 throw new IllegalArgumentException();
146 }
147 }
148
149 private HashEntry getFirst(int hash) {
150 HashEntry[] tab = table;
151 return tab[hash & (tab.length - 1)];
152 }
153
154 /***
155 * Decode the possible DiskSubstitute
156 *
157 * @param object the DiskSubstitute to decode
158 * @return the decoded DiskSubstitute
159 */
160 private Element decode(Object object) {
161 DiskStorageFactory.DiskSubstitute substitute = (DiskStorageFactory.DiskSubstitute) object;
162 return substitute.getFactory().retrieve(substitute);
163 }
164
165 /***
166 * Decode the possible DiskSubstitute, updating the statistics
167 *
168 * @param object the DiskSubstitute to decode
169 * @return the decoded DiskSubstitute
170 */
171 private Element decodeHit(Object object) {
172 DiskStorageFactory.DiskSubstitute substitute = (DiskStorageFactory.DiskSubstitute) object;
173 return substitute.getFactory().retrieve(substitute, this);
174 }
175
176 /***
177 * Free the DiskSubstitute
178 *
179 * @param object the DiskSubstitute to free
180 */
181 private void free(Object object) {
182 free(object, false);
183 }
184
185 /***
186 * Free the DiskSubstitute indicating if it could not be faulted
187 *
188 * @param object the DiskSubstitute to free
189 * @param faultFailure true if the DiskSubstitute should be freed because of a fault failure
190 */
191 private void free(Object object, boolean faultFailure) {
192 DiskStorageFactory.DiskSubstitute diskSubstitute = (DiskStorageFactory.DiskSubstitute) object;
193 diskSubstitute.getFactory().free(writeLock(), diskSubstitute, faultFailure);
194 }
195
196 /***
197 * Get the element mapped to this key (or null if there is no mapping for this key)
198 *
199 * @param key key to lookup
200 * @param hash spread-hash for this key
201 * @return mapped element
202 */
203 Element get(Object key, int hash) {
204 readLock().lock();
205 try {
206
207 if (count != 0) {
208 HashEntry e = getFirst(hash);
209 while (e != null) {
210 if (e.hash == hash && key.equals(e.key)) {
211 return decodeHit(e.getElement());
212 }
213 e = e.next;
214 }
215 }
216 miss();
217 return null;
218 } finally {
219 readLock().unlock();
220 }
221 }
222
223 /***
224 * Return the unretrieved (undecoded) value for this key
225 *
226 * @param key key to lookup
227 * @param hash spread-hash for the key
228 * @return Element or ElementSubstitute
229 */
230 Object unretrievedGet(Object key, int hash) {
231 readLock().lock();
232 try {
233 if (count != 0) {
234 HashEntry e = getFirst(hash);
235 while (e != null) {
236 if (e.hash == hash && key.equals(e.key)) {
237 return e.getElement();
238 }
239 e = e.next;
240 }
241 }
242 } finally {
243 readLock().unlock();
244 }
245 return null;
246 }
247
248 /***
249 * Return true if this segment contains a mapping for this key
250 *
251 * @param key key to check for
252 * @param hash spread-hash for key
253 * @return <code>true</code> if there is a mapping for this key
254 */
255 boolean containsKey(Object key, int hash) {
256 readLock().lock();
257 try {
258
259 if (count != 0) {
260 HashEntry e = getFirst(hash);
261 while (e != null) {
262 if (e.hash == hash && key.equals(e.key)) {
263 return true;
264 }
265 e = e.next;
266 }
267 }
268 return false;
269 } finally {
270 readLock().unlock();
271 }
272 }
273
274 /***
275 * Replace the element mapped to this key only if currently mapped to the given element.
276 *
277 *
278 * @param key key to map the element to
279 * @param hash spread-hash for the key
280 * @param oldElement expected element
281 * @param newElement element to add
282 * @param comparator the comparator to use to compare values
283 * @return <code>true</code> on a successful replace
284 */
285 boolean replace(Object key, int hash, Element oldElement, Element newElement, ElementValueComparator comparator) {
286 boolean installed = false;
287 DiskStorageFactory.DiskSubstitute encoded = disk.create(newElement);
288
289 writeLock().lock();
290 try {
291 HashEntry e = getFirst(hash);
292 while (e != null && (e.hash != hash || !key.equals(e.key))) {
293 e = e.next;
294 }
295
296 boolean replaced = false;
297 if (e != null && comparator.equals(oldElement, decode(e.getElement()))) {
298 replaced = true;
299
300
301
302
303
304 Object onDiskSubstitute = e.getElement();
305
306 long size;
307 size = onHeapPoolAccessor.replace(Role.VALUE, onDiskSubstitute, encoded, cachePinned);
308 if (size == Long.MAX_VALUE) {
309 LOG.debug("replace3 failed to add on heap");
310 free(encoded);
311 return false;
312 } else {
313 LOG.debug("replace3 added {} on heap", size);
314 }
315
316 e.setElement(encoded);
317 installed = true;
318 free(onDiskSubstitute);
319
320 if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) {
321 size = onDiskPoolAccessor.delete(key, null, onDiskSubstitute);
322 LOG.debug("replace3 removed {} from disk", size);
323 }
324 } else {
325 free(encoded);
326 }
327 return replaced;
328 } finally {
329 writeLock().unlock();
330
331 if (installed) {
332 encoded.installed();
333 }
334 }
335 }
336
337 /***
338 * Replace the entry for this key only if currently mapped to some element.
339 *
340 * @param key key to map the element to
341 * @param hash spread-hash for the key
342 * @param newElement element to add
343 * @return previous element mapped to this key
344 */
345 Element replace(Object key, int hash, Element newElement) {
346 boolean installed = false;
347 DiskStorageFactory.DiskSubstitute encoded = disk.create(newElement);
348
349 writeLock().lock();
350 try {
351 HashEntry e = getFirst(hash);
352 while (e != null && (e.hash != hash || !key.equals(e.key))) {
353 e = e.next;
354 }
355
356 Element oldElement = null;
357 if (e != null) {
358 Object onDiskSubstitute = e.getElement();
359
360 long size;
361 size = onHeapPoolAccessor.replace(Role.VALUE, onDiskSubstitute, encoded, cachePinned);
362 if (size == Long.MAX_VALUE) {
363 LOG.debug("replace2 failed to add on heap");
364 free(encoded);
365 return null;
366 } else {
367 LOG.debug("replace2 added {} on heap", size);
368 }
369
370 e.setElement(encoded);
371 installed = true;
372 oldElement = decode(onDiskSubstitute);
373 free(onDiskSubstitute);
374
375 if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) {
376 size = onDiskPoolAccessor.delete(key, null, onDiskSubstitute);
377 LOG.debug("replace2 removed {} from disk", size);
378 }
379 } else {
380 free(encoded);
381 }
382
383 return oldElement;
384 } finally {
385 writeLock().unlock();
386
387 if (installed) {
388 encoded.installed();
389 }
390 }
391 }
392
393 /***
394 * Add the supplied mapping.
395 * <p>
396 * The supplied element is substituted using the primary element proxy factory
397 * before being stored in the cache. If <code>onlyIfAbsent</code> is set
398 * then the mapping will only be added if no element is currently mapped
399 * to that key.
400 *
401 * @param key key to map the element to
402 * @param hash spread-hash for the key
403 * @param element element to store
404 * @param onlyIfAbsent if true does not replace existing mappings
405 * @return previous element mapped to this key
406 */
407 Element put(Object key, int hash, Element element, boolean onlyIfAbsent) {
408 boolean installed = false;
409 DiskStorageFactory.DiskSubstitute encoded = disk.create(element);
410
411 writeLock().lock();
412 try {
413 long size;
414 size = onHeapPoolAccessor.add(key, encoded, HashEntry.newHashEntry(key, hash, null, null), cachePinned);
415 if (size < 0) {
416 LOG.debug("put failed to add on heap");
417 return null;
418 } else {
419 LOG.debug("put added {} on heap", size);
420 }
421
422
423 if (count + 1 > threshold) {
424 rehash();
425 }
426 HashEntry[] tab = table;
427 int index = hash & (tab.length - 1);
428 HashEntry first = tab[index];
429 HashEntry e = first;
430 while (e != null && (e.hash != hash || !key.equals(e.key))) {
431 e = e.next;
432 }
433
434 Element oldElement;
435 if (e != null) {
436 Object onDiskSubstitute = e.getElement();
437 if (!onlyIfAbsent) {
438 e.setElement(encoded);
439 installed = true;
440 oldElement = decode(onDiskSubstitute);
441
442 free(onDiskSubstitute);
443 size = onHeapPoolAccessor.delete(key, onDiskSubstitute, HashEntry.newHashEntry(key, hash, null, null));
444 LOG.debug("put updated, deleted {} on heap", size);
445
446 if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) {
447 size = onDiskPoolAccessor.delete(key, null, onDiskSubstitute);
448 LOG.debug("put updated, deleted {} on disk", size);
449 }
450 } else {
451 oldElement = decode(onDiskSubstitute);
452
453 free(encoded);
454 size = onHeapPoolAccessor.delete(key, encoded, HashEntry.newHashEntry(key, hash, null, null));
455 LOG.debug("put if absent failed, deleted {} on heap", size);
456 }
457 } else {
458 oldElement = null;
459 ++modCount;
460 tab[index] = HashEntry.newHashEntry(key, hash, first, encoded);
461 installed = true;
462
463 count = count + 1;
464 }
465 return oldElement;
466
467 } finally {
468 writeLock().unlock();
469
470 if (installed) {
471 encoded.installed();
472 }
473 }
474 }
475
476
477 /***
478 * Add the supplied pre-encoded mapping.
479 * <p>
480 * The supplied encoded element is directly inserted into the segment
481 * if there is no other mapping for this key.
482 *
483 * @param key key to map the element to
484 * @param hash spread-hash for the key
485 * @param encoded encoded element to store
486 * @return <code>true</code> if the encoded element was installed
487 * @throws IllegalArgumentException if the supplied key is already present
488 */
489 boolean putRawIfAbsent(Object key, int hash, Object encoded) throws IllegalArgumentException {
490 writeLock().lock();
491 try {
492 if (!onDiskPoolAccessor.canAddWithoutEvicting(key, null, encoded)) {
493 return false;
494 }
495 if (onHeapPoolAccessor.add(key, encoded, HashEntry.newHashEntry(key, hash, null, null), cachePinned) < 0) {
496 return false;
497 }
498 if (onDiskPoolAccessor.add(key, null, encoded, cachePinned) < 0) {
499 onHeapPoolAccessor.delete(key, encoded, HashEntry.newHashEntry(key, hash, null, null));
500 return false;
501 }
502
503
504 if (count + 1 > threshold) {
505 rehash();
506 }
507 HashEntry[] tab = table;
508 int index = hash & (tab.length - 1);
509 HashEntry first = tab[index];
510 HashEntry e = first;
511 while (e != null && (e.hash != hash || !key.equals(e.key))) {
512 e = e.next;
513 }
514
515 if (e == null) {
516 ++modCount;
517 tab[index] = HashEntry.newHashEntry(key, hash, first, encoded);
518
519 count = count + 1;
520 return true;
521 } else {
522 onHeapPoolAccessor.delete(key, encoded, HashEntry.newHashEntry(key, hash, null, null));
523 onDiskPoolAccessor.delete(key, null, encoded);
524 throw new IllegalArgumentException("Duplicate key detected");
525 }
526 } finally {
527 writeLock().unlock();
528 }
529 }
530
531 private void rehash() {
532 HashEntry[] oldTable = table;
533 int oldCapacity = oldTable.length;
534 if (oldCapacity >= MAXIMUM_CAPACITY) {
535 return;
536 }
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552 HashEntry[] newTable = new HashEntry[oldCapacity << 1];
553 threshold = (int)(newTable.length * LOAD_FACTOR);
554 int sizeMask = newTable.length - 1;
555 for (int i = 0; i < oldCapacity; i++) {
556
557
558 HashEntry e = oldTable[i];
559
560 if (e != null) {
561 HashEntry next = e.next;
562 int idx = e.hash & sizeMask;
563
564
565 if (next == null) {
566 newTable[idx] = e;
567 } else {
568
569 HashEntry lastRun = e;
570 int lastIdx = idx;
571 for (HashEntry last = next;
572 last != null;
573 last = last.next) {
574 int k = last.hash & sizeMask;
575 if (k != lastIdx) {
576 lastIdx = k;
577 lastRun = last;
578 }
579 }
580 newTable[lastIdx] = lastRun;
581
582
583 for (HashEntry p = e; p != lastRun; p = p.next) {
584 int k = p.hash & sizeMask;
585 HashEntry n = newTable[k];
586 newTable[k] = HashEntry.newHashEntry(p.key, p.hash, n, p.getElement());
587 }
588 }
589 }
590 }
591 table = newTable;
592 }
593
594 /***
595 * Remove the matching mapping.
596 * <p>
597 * If <code>value</code> is <code>null</code> then match on the key only,
598 * else match on both the key and the value.
599 *
600 *
601 * @param key key to match against
602 * @param hash spread-hash for the key
603 * @param value optional value to match against
604 * @param comparator the comparator to use to compare values
605 * @return removed element
606 */
607 Element remove(Object key, int hash, Element value, ElementValueComparator comparator) {
608 writeLock().lock();
609 try {
610 HashEntry[] tab = table;
611 int index = hash & (tab.length - 1);
612 HashEntry first = tab[index];
613 HashEntry e = first;
614 while (e != null && (e.hash != hash || !key.equals(e.key))) {
615 e = e.next;
616 }
617
618 Element oldValue = null;
619 if (e != null) {
620 oldValue = decode(e.getElement());
621 if (value == null || comparator.equals(value, oldValue)) {
622
623
624
625 ++modCount;
626 HashEntry newFirst = e.next;
627 for (HashEntry p = first; p != e; p = p.next) {
628 newFirst = HashEntry.newHashEntry(p.key, p.hash, newFirst, p.getElement());
629 }
630 tab[index] = newFirst;
631
632
633
634
635
636 Object onDiskSubstitute = e.getElement();
637 free(onDiskSubstitute);
638
639 long size;
640 size = onHeapPoolAccessor.delete(key, onDiskSubstitute, HashEntry.newHashEntry(key, hash, null, null));
641 LOG.debug("remove deleted {} from heap", size);
642
643 if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) {
644 size = onDiskPoolAccessor.delete(key, null, onDiskSubstitute);
645 LOG.debug("remove deleted {} from disk", size);
646 }
647
648
649 count = count - 1;
650 } else {
651 oldValue = null;
652 }
653 }
654
655 if (oldValue == null) {
656 LOG.debug("remove deleted nothing");
657 }
658
659 return oldValue;
660 } finally {
661 writeLock().unlock();
662 }
663 }
664
665 /***
666 * Removes all mappings from this segment.
667 */
668 void clear() {
669 writeLock().lock();
670 try {
671 if (count != 0) {
672 HashEntry[] tab = table;
673 for (int i = 0; i < tab.length; i++) {
674 for (HashEntry e = tab[i]; e != null; e = e.next) {
675 free(e.getElement());
676 }
677 tab[i] = null;
678 }
679 ++modCount;
680
681 count = 0;
682 }
683 onHeapPoolAccessor.clear();
684 LOG.debug("cleared heap usage");
685 onDiskPoolAccessor.clear();
686 LOG.debug("cleared disk usage");
687 } finally {
688 writeLock().unlock();
689 }
690 }
691
692 /***
693 * Try to atomically switch (CAS) the <code>expect</code> representation of this element for the
694 * <code>fault</code> representation.
695 * <p>
696 * A successful switch will return <code>true</code>, and free the replaced element/element-proxy.
697 * A failed switch will return <code>false</code> and free the element/element-proxy which was not
698 * installed. Unlike <code>fault</code> this method can return <code>false</code> if the object
699 * could not be installed due to lock contention.
700 *
701 * @param key key to which this element (proxy) is mapped
702 * @param hash the hash of the key
703 * @param expect element (proxy) expected
704 * @param fault element (proxy) to install
705 * @return <code>true</code> if <code>fault</code> was installed
706 */
707 boolean fault(Object key, int hash, Object expect, Object fault) {
708 boolean installed = false;
709
710 writeLock().lock();
711 try {
712 long size;
713 size = onHeapPoolAccessor.replace(Role.VALUE, expect, fault, cachePinned);
714 if (size == Long.MAX_VALUE) {
715 remove(key, hash, null, null);
716 return false;
717 } else {
718 LOG.debug("fault removed {} from heap", size);
719 }
720 size = onDiskPoolAccessor.add(key, null, fault, cachePinned);
721 if (size < 0) {
722
723
724 long deleteSize = onHeapPoolAccessor.replace(Role.VALUE, fault, expect, true);
725 LOG.debug("fault failed to add {} on disk, deleted {} from heap", size, deleteSize);
726 remove(key, hash, null, null);
727 return false;
728 } else {
729 LOG.debug("fault added {} on disk", size);
730 }
731
732 installed = install(key, hash, expect, fault);
733 if (installed) {
734 return true;
735 } else {
736
737
738 size = onHeapPoolAccessor.replace(Role.VALUE, fault, expect, true);
739 LOG.debug("fault installation failed, deleted {} from heap", size);
740 size = onDiskPoolAccessor.delete(key, null, fault);
741 LOG.debug("fault installation failed deleted {} from disk", size);
742
743 free(fault, true);
744 return false;
745 }
746 } finally {
747 writeLock().unlock();
748
749 if ((installed && fault instanceof DiskStorageFactory.DiskSubstitute)) {
750 ((DiskStorageFactory.DiskSubstitute) fault).installed();
751 }
752 }
753 }
754
755 /***
756 * Count the number of elements which have been added to the store but haven't been written to disk yet
757 *
758 * @return the number of elements which have been added to the store but haven't been written to disk yet
759 */
760 int countOnHeap() {
761 readLock().lock();
762 try {
763 int result = 0;
764
765 if (count != 0) {
766 for (HashEntry hashEntry : table) {
767 for (HashEntry e = hashEntry; e != null; e = e.next) {
768 if (e.getElement() instanceof DiskStorageFactory.Placeholder) {
769 result++;
770 }
771 }
772 }
773 }
774
775 return result;
776 } finally {
777 readLock().unlock();
778 }
779 }
780
781 private boolean install(Object key, int hash, Object expect, Object fault) {
782 if (count != 0) {
783 for (HashEntry e = getFirst(hash); e != null; e = e.next) {
784 if (e.hash == hash && key.equals(e.key)) {
785 if (e.casElement(expect, fault)) {
786 free(expect);
787 return true;
788 }
789 }
790 }
791 }
792 return false;
793 }
794
795 /***
796 * Remove the matching mapping. Unlike the {@link net.sf.ehcache.store.disk.Segment#remove(Object, int, net.sf.ehcache.Element, net.sf.ehcache.store.ElementValueComparator)} method
797 * evict does referential comparison of the unretrieved substitute against the argument value.
798 *
799 * @param key key to match against
800 * @param hash spread-hash for the key
801 * @param value optional value to match against
802 * @return <code>true</code> on a successful remove
803 */
804 Element evict(Object key, int hash, Object value) {
805 if (writeLock().tryLock()) {
806 try {
807 HashEntry[] tab = table;
808 int index = hash & (tab.length - 1);
809 HashEntry first = tab[index];
810 HashEntry e = first;
811 while (e != null && (e.hash != hash || !key.equals(e.key))) {
812 e = e.next;
813 }
814
815 if (e != null && (value == null || value == e.getElement())) {
816
817
818
819 ++modCount;
820 HashEntry newFirst = e.next;
821 for (HashEntry p = first; p != e; p = p.next) {
822 newFirst = HashEntry.newHashEntry(p.key, p.hash, newFirst, p.getElement());
823 }
824 tab[index] = newFirst;
825
826
827
828
829
830 Object v = e.getElement();
831 Element toReturn = decode(v);
832
833 free(v);
834
835 long size;
836 if (v instanceof DiskStorageFactory.DiskMarker) {
837 size = onDiskPoolAccessor.delete(key, null, v);
838 LOG.debug("evicted {} from disk", size);
839 }
840 size = onHeapPoolAccessor.delete(key, v, HashEntry.newHashEntry(key, hash, null, null));
841 LOG.debug("evicted {} from heap", size);
842
843
844 count = count - 1;
845 return toReturn;
846 }
847
848 return null;
849 } finally {
850 writeLock().unlock();
851 }
852 } else {
853 return null;
854 }
855 }
856
857 /***
858 * Select a random sample of elements generated by the supplied factory.
859 *
860 * @param filter filter of substitute types
861 * @param sampleSize minimum number of elements to return
862 * @param sampled collection in which to place the elements
863 * @param seed random seed for the selection
864 */
865 void addRandomSample(ElementSubstituteFilter filter, int sampleSize, Collection<DiskStorageFactory.DiskSubstitute> sampled, int seed) {
866 final HashEntry[] tab = table;
867 final int tableStart = seed & (tab.length - 1);
868 int tableIndex = tableStart;
869 do {
870 for (HashEntry e = tab[tableIndex]; e != null; e = e.next) {
871 Object value = e.getElement();
872 if (filter.allows(value)) {
873 sampled.add((DiskStorageFactory.DiskSubstitute) value);
874 }
875 }
876
877 if (sampled.size() >= sampleSize) {
878 return;
879 }
880
881
882 tableIndex = (tableIndex + 1) & (tab.length - 1);
883 } while (tableIndex != tableStart);
884 }
885
886 /***
887 * Creates an iterator over the HashEntry objects within this Segment.
888 * @return an iterator over the HashEntry objects within this Segment.
889 */
890 Iterator<HashEntry> hashIterator() {
891 return new HashIterator();
892 }
893
894 @Override
895 public String toString() {
896 return super.toString() + " count: " + count;
897 }
898
899 /***
900 * An iterator over the HashEntry objects within this Segment.
901 */
902 final class HashIterator implements Iterator<HashEntry> {
903 private int nextTableIndex;
904 private final HashEntry[] ourTable;
905 private HashEntry nextEntry;
906 private HashEntry lastReturned;
907
908 private HashIterator() {
909 if (count != 0) {
910 ourTable = table;
911 for (int j = ourTable.length - 1; j >= 0; --j) {
912 nextEntry = ourTable[j];
913 if (nextEntry != null) {
914 nextTableIndex = j - 1;
915 return;
916 }
917 }
918 } else {
919 ourTable = null;
920 nextTableIndex = -1;
921 }
922 advance();
923 }
924
925 private void advance() {
926 if (nextEntry != null) {
927 nextEntry = nextEntry.next;
928 if (nextEntry != null) {
929 return;
930 }
931 }
932
933 while (nextTableIndex >= 0) {
934 nextEntry = ourTable[nextTableIndex--];
935 if (nextEntry != null) {
936 return;
937 }
938 }
939 }
940
941 /***
942 * {@inheritDoc}
943 */
944 public boolean hasNext() {
945 return nextEntry != null;
946 }
947
948 /***
949 * {@inheritDoc}
950 */
951 public HashEntry next() {
952 if (nextEntry == null) {
953 throw new NoSuchElementException();
954 }
955 lastReturned = nextEntry;
956 advance();
957 return lastReturned;
958 }
959
960 /***
961 * {@inheritDoc}
962 */
963 public void remove() {
964 if (lastReturned == null) {
965 throw new IllegalStateException();
966 }
967 Segment.this.remove(lastReturned.key, lastReturned.hash, null, null);
968 lastReturned = null;
969 }
970 }
971
972 /***
973 * Return the disk hit rate
974 * @return the disk hit rate
975 */
976 public float getDiskHitRate() {
977 return diskHitRate.getRate();
978 }
979
980 /***
981 * Return the disk miss rate
982 * @return the disk miss rate
983 */
984 public float getDiskMissRate() {
985 return diskMissRate.getRate();
986 }
987
988 /***
989 * Record a hit in the disk tier
990 */
991 protected void diskHit() {
992 diskHitRate.event();
993 }
994
995 /***
996 * Record a miss in the disk tier
997 */
998 protected void miss() {
999 diskMissRate.event();
1000 }
1001 }