View Javadoc

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             // read-volatile
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             // read-volatile
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                  * make sure we re-get from the HashEntry - since the decode in the conditional
301                  * may have faulted in a different type - we must make sure we know what type
302                  * to do the increment/decrement on.
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             // ensure capacity
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                 // write-volatile
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             // ensure capacity
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                 // write-volatile
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          * Reclassify nodes in each list to new Map.  Because we are
540          * using power-of-two expansion, the elements from each bin
541          * must either stay at same index, or move with a power of two
542          * offset. We eliminate unnecessary node creation by catching
543          * cases where old nodes can be reused because their next
544          * fields won't change. Statistically, at the default
545          * threshold, only about one-sixth of them need cloning when
546          * a table doubles. The nodes they replace will be garbage
547          * collectable as soon as they are no longer referenced by any
548          * reader thread that may be in the midst of traversing table
549          * right now.
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             // We need to guarantee that any existing reads of old Map can
557             //  proceed. So we cannot yet null out each bin.
558             HashEntry e = oldTable[i];
559 
560             if (e != null) {
561                 HashEntry next = e.next;
562                 int idx = e.hash & sizeMask;
563 
564                 //  Single node on list
565                 if (next == null) {
566                     newTable[idx] = e;
567                 } else {
568                     // Reuse trailing consecutive sequence at same slot
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                     // Clone all remaining nodes
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                     // All entries following removed node can stay
623                     // in list, but all preceding ones need to be
624                     // cloned.
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                      * make sure we re-get from the HashEntry - since the decode in the conditional
633                      * may have faulted in a different type - we must make sure we know what type
634                      * to do the free on.
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                     // write-volatile
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                 // write-volatile
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                 //todo: replace must not fail here but it could if the memory freed by the previous replace has been stolen in the meantime
723                 // that's why it is forced, even if that could make the pool go over limit
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                 //todo: replace must not fail here but it could if the memory freed by the previous replace has been stolen in the meantime
737                 // that's why it is forced, even if that could make the pool go over limit
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                     // All entries following removed node can stay
817                     // in list, but all preceding ones need to be
818                     // cloned.
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                      * make sure we re-get from the HashEntry - since the decode in the conditional
827                      * may have faulted in a different type - we must make sure we know what type
828                      * to do the free on.
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                     // write-volatile
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             //move to next table slot
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 }