001package org.cache2k.impl;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import org.cache2k.BulkCacheSource;
026import org.cache2k.CacheEntry;
027import org.cache2k.CacheException;
028import org.cache2k.CacheMisconfigurationException;
029import org.cache2k.ClosableIterator;
030import org.cache2k.EntryExpiryCalculator;
031import org.cache2k.ExceptionExpiryCalculator;
032import org.cache2k.ExperimentalBulkCacheSource;
033import org.cache2k.Cache;
034import org.cache2k.CacheConfig;
035import org.cache2k.RefreshController;
036import org.cache2k.StorageConfiguration;
037import org.cache2k.ValueWithExpiryTime;
038import org.cache2k.impl.threading.Futures;
039import org.cache2k.impl.threading.LimitedPooledExecutor;
040import org.cache2k.impl.timer.GlobalTimerService;
041import org.cache2k.impl.timer.TimerService;
042import org.cache2k.impl.util.ThreadDump;
043import org.cache2k.CacheSourceWithMetaInfo;
044import org.cache2k.CacheSource;
045import org.cache2k.PropagatedCacheException;
046
047import org.cache2k.storage.PurgeableStorage;
048import org.cache2k.storage.StorageEntry;
049import org.cache2k.impl.util.Log;
050import org.cache2k.impl.util.TunableConstants;
051import org.cache2k.impl.util.TunableFactory;
052
053import java.lang.reflect.Array;
054import java.security.SecureRandom;
055import java.util.AbstractList;
056import java.util.AbstractMap;
057import java.util.AbstractSet;
058import java.util.Arrays;
059import java.util.BitSet;
060import java.util.Date;
061import java.util.HashSet;
062import java.util.Iterator;
063import java.util.List;
064import java.util.Map;
065import java.util.Random;
066import java.util.Set;
067import java.util.Timer;
068import java.util.concurrent.Future;
069import java.util.concurrent.FutureTask;
070import java.util.concurrent.TimeUnit;
071import java.util.concurrent.TimeoutException;
072
073import static org.cache2k.impl.util.Util.*;
074
075/**
076 * Foundation for all cache variants. All common functionality is in here.
077 * For a (in-memory) cache we need three things: a fast hash table implementation, an
078 * LRU list (a simple double linked list), and a fast timer.
079 * The variants implement different eviction strategies.
080 *
081 * <p>Locking: The cache has a single structure lock obtained via {@link #lock} and also
082 * locks on each entry for operations on it. Though, mutation operations that happen on a
083 * single entry get serialized.
084 *
085 * @author Jens Wilke; created: 2013-07-09
086 */
087@SuppressWarnings({"unchecked", "SynchronizationOnLocalVariableOrMethodParameter"})
088public abstract class BaseCache<E extends Entry, K, T>
089  implements Cache<K, T>, CanCheckIntegrity, Iterable<CacheEntry<K, T>>, StorageAdapter.Parent {
090
091  static final Random SEED_RANDOM = new Random(new SecureRandom().nextLong());
092  static int cacheCnt = 0;
093
094  protected static final Tunable TUNABLE = TunableFactory.get(Tunable.class);
095
096  /**
097   * Instance of expiry calculator that extracts the expiry time from the value.
098   */
099  final static EntryExpiryCalculator<?, ValueWithExpiryTime> ENTRY_EXPIRY_CALCULATOR_FROM_VALUE = new
100    EntryExpiryCalculator<Object, ValueWithExpiryTime>() {
101      @Override
102      public long calculateExpiryTime(
103          Object _key, ValueWithExpiryTime _value, long _fetchTime,
104          CacheEntry<Object, ValueWithExpiryTime> _oldEntry) {
105        return _value.getCacheExpiryTime();
106      }
107    };
108
109  protected int hashSeed;
110
111  {
112    if (TUNABLE.disableHashRandomization) {
113      hashSeed = TUNABLE.hashSeed;
114    } else {
115      hashSeed = SEED_RANDOM.nextInt();
116    }
117  }
118
119  /** Maximum amount of elements in cache */
120  protected int maxSize = 5000;
121
122  protected String name;
123  protected CacheManagerImpl manager;
124  protected CacheSourceWithMetaInfo<K, T> source;
125  /** Statistics */
126
127  /** Time in milliseconds we keep an element */
128  protected long maxLinger = 10 * 60 * 1000;
129
130  protected long exceptionMaxLinger = 1 * 60 * 1000;
131
132  protected EntryExpiryCalculator<K, T> entryExpiryCalculator;
133
134  protected ExceptionExpiryCalculator<K> exceptionExpiryCalculator;
135
136  protected Info info;
137
138  protected long clearedTime = 0;
139  protected long startedTime;
140  protected long touchedTime;
141  protected int timerCancelCount = 0;
142
143  protected long keyMutationCount = 0;
144  protected long putCnt = 0;
145  protected long putNewEntryCnt = 0;
146  protected long removedCnt = 0;
147  protected long expiredKeptCnt = 0;
148  protected long expiredRemoveCnt = 0;
149  protected long evictedCnt = 0;
150  protected long refreshCnt = 0;
151  protected long suppressedExceptionCnt = 0;
152  protected long fetchExceptionCnt = 0;
153  /* that is a miss, but a hit was already counted. */
154  protected long peekHitNotFreshCnt = 0;
155  /* no heap hash hit */
156  protected long peekMissCnt = 0;
157
158  protected long fetchCnt = 0;
159
160  protected long fetchButHitCnt = 0;
161  protected long bulkGetCnt = 0;
162  protected long fetchMillis = 0;
163  protected long refreshHitCnt = 0;
164  protected long newEntryCnt = 0;
165
166  /**
167   * Loaded from storage, but the entry was not fresh and cannot be returned.
168   */
169  protected long loadNonFreshCnt = 0;
170
171  /**
172   * Entry was loaded from storage and fresh.
173   */
174  protected long loadHitCnt = 0;
175
176  /**
177   * Separate counter for loaded entries that needed a fetch.
178   */
179  protected long loadNonFreshAndFetchedCnt;
180
181  protected long refreshSubmitFailedCnt = 0;
182
183  /**
184   * An exception that should not have happened and was not thrown to the
185   * application. Only used for the refresh thread yet.
186   */
187  protected long internalExceptionCnt = 0;
188
189  /**
190   * Needed to correct the counter invariants, because during eviction the entry
191   * might be removed from the replacement list, but still in the hash.
192   */
193  protected int evictedButInHashCnt = 0;
194
195  /**
196   * Storage did not contain the requested entry.
197   */
198  protected long loadMissCnt = 0;
199
200  /**
201   * A newly inserted entry was removed by the eviction without the fetch to complete.
202   */
203  protected long virginEvictCnt = 0;
204
205  protected int maximumBulkFetchSize = 100;
206
207  /**
208   * Structure lock of the cache. Every operation that needs a consistent structure
209   * of the cache or modifies it needs to synchronize on this. Since this is a global
210   * lock, locking on it should be avoided and any operation under the lock should be
211   * quick.
212   */
213  protected final Object lock = new Object();
214
215  protected CacheRefreshThreadPool refreshPool;
216
217  protected Hash<E> mainHashCtrl;
218  protected E[] mainHash;
219
220  protected Hash<E> refreshHashCtrl;
221  protected E[] refreshHash;
222
223  protected ExperimentalBulkCacheSource<K, T> experimentalBulkCacheSource;
224
225  protected BulkCacheSource<K, T> bulkCacheSource;
226
227  protected Timer timer;
228
229  /** Stuff that we need to wait for before shutdown may complete */
230  protected Futures.WaitForAllFuture<?> shutdownWaitFuture;
231
232  protected boolean shutdownInitiated = false;
233
234  /**
235   * Flag during operation that indicates, that the cache is full and eviction needs
236   * to be done. Eviction is only allowed to happen after an entry is fetched, so
237   * at the end of an cache operation that increased the entry count we check whether
238   * something needs to be evicted.
239   */
240  protected boolean evictionNeeded = false;
241
242  protected Class keyType;
243
244  protected Class valueType;
245
246  protected StorageAdapter storage;
247
248  protected TimerService timerService = GlobalTimerService.getInstance();
249
250  /**
251   * Stops creation of new entries when clear is ongoing.
252   */
253  protected boolean waitForClear = false;
254
255  private int featureBits = 0;
256
257  private static final int SHARP_TIMEOUT_FEATURE = 1;
258  private static final int KEEP_AFTER_EXPIRED = 2;
259  private static final int SUPPRESS_EXCEPTIONS = 4;
260
261  protected final boolean hasSharpTimeout() {
262    return (featureBits & SHARP_TIMEOUT_FEATURE) > 0;
263  }
264
265  protected final boolean hasKeepAfterExpired() {
266    return (featureBits & KEEP_AFTER_EXPIRED) > 0;
267  }
268
269  protected final boolean hasSuppressExceptions() {
270    return (featureBits & SUPPRESS_EXCEPTIONS) > 0;
271  }
272
273  protected final void setFeatureBit(int _bitmask, boolean _flag) {
274    if (_flag) {
275      featureBits |= _bitmask;
276    } else {
277      featureBits &= ~_bitmask;
278    }
279  }
280
281  /**
282   * Enabling background refresh means also serving expired values.
283   */
284  protected final boolean hasBackgroundRefreshAndServesExpiredValues() {
285    return refreshPool != null;
286  }
287
288  /**
289   * Returns name of the cache with manager name.
290   */
291  protected String getCompleteName() {
292    if (manager != null) {
293      return manager.getName() + ":" + name;
294    }
295    return name;
296  }
297
298  /**
299   * Normally a cache itself logs nothing, so just construct when needed.
300   */
301  protected Log getLog() {
302    return
303      Log.getLog(Cache.class.getName() + '/' + getCompleteName());
304  }
305
306  @Override
307  public void resetStorage(StorageAdapter _from, StorageAdapter to) {
308    synchronized (lock) {
309      storage = to;
310    }
311  }
312
313  /** called via reflection from CacheBuilder */
314  public void setCacheConfig(CacheConfig c) {
315    valueType = c.getValueType();
316    keyType = c.getKeyType();
317    if (name != null) {
318      throw new IllegalStateException("already configured");
319    }
320    setName(c.getName());
321    maxSize = c.getMaxSize();
322    if (c.getHeapEntryCapacity() >= 0) {
323      maxSize = c.getHeapEntryCapacity();
324    }
325    if (c.isBackgroundRefresh()) {
326      refreshPool = CacheRefreshThreadPool.getInstance();
327    }
328    long _expiryMillis  = c.getExpiryMillis();
329    if (_expiryMillis == Long.MAX_VALUE || _expiryMillis < 0) {
330      maxLinger = -1;
331    } else if (_expiryMillis >= 0) {
332      maxLinger = _expiryMillis;
333    }
334    long _exceptionExpiryMillis = c.getExceptionExpiryMillis();
335    if (_exceptionExpiryMillis == -1) {
336      if (maxLinger == -1) {
337        exceptionMaxLinger = -1;
338      } else {
339        exceptionMaxLinger = maxLinger / 10;
340      }
341    } else {
342      exceptionMaxLinger = _exceptionExpiryMillis;
343    }
344    setFeatureBit(KEEP_AFTER_EXPIRED, c.isKeepDataAfterExpired());
345    setFeatureBit(SHARP_TIMEOUT_FEATURE, c.isSharpExpiry());
346    setFeatureBit(SUPPRESS_EXCEPTIONS, c.isSuppressExceptions());
347    /*
348    if (c.isPersistent()) {
349      storage = new PassingStorageAdapter();
350    }
351    -*/
352    List<StorageConfiguration> _stores = c.getStorageModules();
353    if (_stores.size() == 1) {
354      StorageConfiguration cfg = _stores.get(0);
355      if (cfg.getEntryCapacity() < 0) {
356        cfg.setEntryCapacity(c.getMaxSize());
357      }
358      storage = new PassingStorageAdapter(this, c, _stores.get(0));
359    } else if (_stores.size() > 1) {
360      throw new UnsupportedOperationException("no aggregation support yet");
361    }
362    if (ValueWithExpiryTime.class.isAssignableFrom(c.getValueType()) &&
363        entryExpiryCalculator == null)  {
364      entryExpiryCalculator =
365        (EntryExpiryCalculator<K, T>)
366        ENTRY_EXPIRY_CALCULATOR_FROM_VALUE;
367    }
368  }
369
370  public void setEntryExpiryCalculator(EntryExpiryCalculator<K, T> v) {
371    entryExpiryCalculator = v;
372  }
373
374  public void setExceptionExpiryCalculator(ExceptionExpiryCalculator<K> v) {
375    exceptionExpiryCalculator = v;
376  }
377
378  /** called via reflection from CacheBuilder */
379  public void setRefreshController(final RefreshController<T> lc) {
380    entryExpiryCalculator = new EntryExpiryCalculator<K, T>() {
381      @Override
382      public long calculateExpiryTime(K _key, T _value, long _fetchTime, CacheEntry<K, T> _oldEntry) {
383        if (_oldEntry != null) {
384          return lc.calculateNextRefreshTime(_oldEntry.getValue(), _value, _oldEntry.getLastModification(), _fetchTime);
385        } else {
386          return lc.calculateNextRefreshTime(null, _value, 0L, _fetchTime);
387        }
388      }
389    };
390  }
391
392  @SuppressWarnings("unused")
393  public void setSource(CacheSourceWithMetaInfo<K, T> eg) {
394    source = eg;
395  }
396
397  @SuppressWarnings("unused")
398  public void setSource(final CacheSource<K, T> g) {
399    if (g != null) {
400      source = new CacheSourceWithMetaInfo<K, T>() {
401        @Override
402        public T get(K key, long _currentTime, T _previousValue, long _timeLastFetched) throws Throwable {
403          return g.get(key);
404        }
405      };
406    }
407  }
408
409  @SuppressWarnings("unused")
410  public void setExperimentalBulkCacheSource(ExperimentalBulkCacheSource<K, T> g) {
411    experimentalBulkCacheSource = g;
412    if (source == null) {
413      source = new CacheSourceWithMetaInfo<K, T>() {
414        @Override
415        public T get(K key, long _currentTime, T _previousValue, long _timeLastFetched) throws Throwable {
416          K[] ka = (K[]) Array.newInstance(keyType, 1);
417          ka[0] = key;
418          T[] ra = (T[]) Array.newInstance(valueType, 1);
419          BitSet _fetched = new BitSet(1);
420          experimentalBulkCacheSource.getBulk(ka, ra, _fetched, 0, 1);
421          return ra[0];
422        }
423      };
424    }
425  }
426
427  public void setBulkCacheSource(BulkCacheSource<K, T> s) {
428    bulkCacheSource = s;
429    if (source == null) {
430      source = new CacheSourceWithMetaInfo<K, T>() {
431        @Override
432        public T get(final K key, final long _currentTime, final T _previousValue, final long _timeLastFetched) throws Throwable {
433          final CacheEntry<K, T> entry = new CacheEntry<K, T>() {
434            @Override
435            public K getKey() {
436              return key;
437            }
438
439            @Override
440            public T getValue() {
441              return _previousValue;
442            }
443
444            @Override
445            public Throwable getException() {
446              return null;
447            }
448
449            @Override
450            public long getLastModification() {
451              return _timeLastFetched;
452            }
453          };
454          List<CacheEntry<K, T>> _entryList = new AbstractList<CacheEntry<K, T>>() {
455            @Override
456            public CacheEntry<K, T> get(int index) {
457              return entry;
458            }
459
460            @Override
461            public int size() {
462              return 1;
463            }
464          };
465          return bulkCacheSource.getValues(_entryList, _currentTime).get(0);
466        }
467      };
468    }
469  }
470
471  /**
472   * Set the name and configure a logging, used within cache construction.
473   */
474  public void setName(String n) {
475    if (n == null) {
476      n = this.getClass().getSimpleName() + "#" + cacheCnt++;
477    }
478    name = n;
479  }
480
481  /**
482   * Set the time in seconds after which the cache does an refresh of the
483   * element. -1 means the element will be hold forever.
484   * 0 means the element will not be cached at all.
485   */
486  public void setExpirySeconds(int s) {
487    if (s < 0 || s == Integer.MAX_VALUE) {
488      maxLinger = -1;
489      return;
490    }
491    maxLinger = s * 1000;
492  }
493
494  public String getName() {
495    return name;
496  }
497
498  public void setCacheManager(CacheManagerImpl cm) {
499    manager = cm;
500  }
501
502  public StorageAdapter getStorage() { return storage; }
503
504  /**
505   * Registers the cache in a global set for the clearAllCaches function and
506   * registers it with the resource monitor.
507   */
508  public void init() {
509    synchronized (lock) {
510      if (name == null) {
511        name = "" + cacheCnt++;
512      }
513
514      if (storage == null && maxSize == 0) {
515        throw new IllegalArgumentException("maxElements must be >0");
516      }
517      if (storage != null) {
518        bulkCacheSource = null;
519        storage.open();
520      }
521      initializeHeapCache();
522      initTimer();
523      if (refreshPool != null &&
524        source == null) {
525        throw new CacheMisconfigurationException("backgroundRefresh, but no source");
526      }
527      if (refreshPool != null && timer == null) {
528        if (maxLinger == 0) {
529          getLog().warn("Background refresh is enabled, but elements are fetched always. Disable background refresh!");
530        } else {
531          getLog().warn("Background refresh is enabled, but elements are eternal. Disable background refresh!");
532        }
533        refreshPool.destroy();
534        refreshPool = null;
535      }
536    }
537  }
538
539  boolean isNeedingTimer() {
540    return
541        maxLinger > 0 || entryExpiryCalculator != null ||
542        exceptionMaxLinger > 0 || exceptionExpiryCalculator != null;
543  }
544
545  /**
546   * Either add a timer or remove the timer if needed or not needed.
547   */
548  private void initTimer() {
549    if (isNeedingTimer()) {
550      if (timer == null) {
551        timer = new Timer(name, true);
552      }
553    } else {
554      if (timer != null) {
555        timer.cancel();
556        timer = null;
557      }
558    }
559  }
560
561  /**
562   * Clear may be called during operation, e.g. to reset all the cache content. We must make sure
563   * that there is no ongoing operation when we send the clear to the storage. That is because the
564   * storage implementation has a guarantee that there is only one storage operation ongoing for
565   * one entry or key at any time. Clear, of course, affects all entries.
566   */
567  private void processClearWithStorage() {
568    StorageClearTask t = new StorageClearTask();
569    boolean _untouchedHeapCache;
570    synchronized (lock) {
571      checkClosed();
572      waitForClear = true;
573      _untouchedHeapCache = touchedTime == clearedTime && getLocalSize() == 0;
574      if (!storage.checkStorageStillDisconnectedForClear()) {
575        t.allLocalEntries = iterateAllHeapEntries();
576        t.allLocalEntries.setStopOnClear(false);
577      }
578      t.storage = storage;
579      t.storage.disconnectStorageForClear();
580    }
581    try {
582      if (_untouchedHeapCache) {
583        FutureTask<Void> f = new FutureTask<Void>(t);
584        updateShutdownWaitFuture(f);
585        f.run();
586      } else {
587        updateShutdownWaitFuture(manager.getThreadPool().execute(t));
588      }
589    } catch (Exception ex) {
590      throw new CacheStorageException(ex);
591    }
592    synchronized (lock) {
593      if (isClosed()) { throw new CacheClosedException(); }
594      clearLocalCache();
595      waitForClear = false;
596      lock.notifyAll();
597    }
598  }
599
600  protected void updateShutdownWaitFuture(Future<?> f) {
601    synchronized (lock) {
602      if (shutdownWaitFuture == null || shutdownWaitFuture.isDone()) {
603        shutdownWaitFuture = new Futures.WaitForAllFuture(f);
604      } else {
605        shutdownWaitFuture.add((Future) f);
606      }
607    }
608  }
609
610  class StorageClearTask implements LimitedPooledExecutor.NeverRunInCallingTask<Void> {
611
612    ClosableConcurrentHashEntryIterator<Entry> allLocalEntries;
613    StorageAdapter storage;
614
615    @Override
616    public Void call() {
617      try {
618        if (allLocalEntries != null) {
619          waitForEntryOperations();
620        }
621        storage.clearAndReconnect();
622        storage = null;
623        return null;
624      } catch (Throwable t) {
625        if (allLocalEntries != null) {
626          allLocalEntries.close();
627        }
628        getLog().warn("clear exception, when signalling storage", t);
629        storage.disable(t);
630        throw new CacheStorageException(t);
631      }
632    }
633
634    private void waitForEntryOperations() {
635      Iterator<Entry> it = allLocalEntries;
636      while (it.hasNext()) {
637        Entry e = it.next();
638        synchronized (e) { }
639      }
640    }
641  }
642
643  protected void checkClosed() {
644    if (isClosed()) {
645      throw new CacheClosedException();
646    }
647    while (waitForClear) {
648      try {
649        lock.wait();
650      } catch (InterruptedException ignore) { }
651    }
652  }
653
654  public final void clear() {
655    if (storage != null) {
656      processClearWithStorage();
657      return;
658    }
659    synchronized (lock) {
660      checkClosed();
661      clearLocalCache();
662    }
663  }
664
665  protected final void clearLocalCache() {
666    Iterator<Entry> it = iterateAllHeapEntries();
667    int _count = 0;
668    while (it.hasNext()) {
669      Entry e = it.next();
670      e.removedFromList();
671      cancelExpiryTimer(e);
672      _count++;
673    }
674    removedCnt += getLocalSize();
675    initializeHeapCache();
676    clearedTime = System.currentTimeMillis();
677    touchedTime = clearedTime;
678  }
679
680  protected void initializeHeapCache() {
681    if (mainHashCtrl != null) {
682      mainHashCtrl.cleared();
683      refreshHashCtrl.cleared();
684    }
685    mainHashCtrl = new Hash<E>();
686    refreshHashCtrl = new Hash<E>();
687    mainHash = mainHashCtrl.init((Class<E>) newEntry().getClass());
688    refreshHash = refreshHashCtrl.init((Class<E>) newEntry().getClass());
689    if (startedTime == 0) {
690      startedTime = System.currentTimeMillis();
691    }
692    if (timer != null) {
693      timer.cancel();
694      timer = null;
695      initTimer();
696    }
697  }
698
699  public void clearTimingStatistics() {
700    synchronized (lock) {
701      fetchCnt = 0;
702      fetchMillis = 0;
703    }
704  }
705
706  /**
707   * Preparation for shutdown. Cancel all pending timer jobs e.g. for
708   * expiry/refresh or flushing the storage.
709   */
710  Future<Void> cancelTimerJobs() {
711    synchronized (lock) {
712      if (timer != null) {
713        timer.cancel();
714      }
715      Future<Void> _waitFuture = new Futures.BusyWaitFuture<Void>() {
716        @Override
717        public boolean isDone() {
718          synchronized (lock) {
719            return getFetchesInFlight() == 0;
720          }
721        }
722      };
723      if (storage != null) {
724        Future<Void> f = storage.cancelTimerJobs();
725        if (f != null) {
726          _waitFuture = new Futures.WaitForAllFuture(_waitFuture, f);
727        }
728      }
729      return _waitFuture;
730    }
731  }
732
733  public void purge() {
734    if (storage != null) {
735      storage.purge();
736    }
737  }
738
739  public void flush() {
740    if (storage != null) {
741      storage.flush();
742    }
743  }
744
745  protected boolean isClosed() {
746    return shutdownInitiated;
747  }
748
749  @Override
750  public void destroy() {
751    close();
752  }
753
754  @Override
755  public void close() {
756    synchronized (lock) {
757      if (shutdownInitiated) {
758        return;
759      }
760      shutdownInitiated = true;
761    }
762    Future<Void> _await = cancelTimerJobs();
763    try {
764      _await.get(TUNABLE.waitForTimerJobsSeconds, TimeUnit.SECONDS);
765    } catch (TimeoutException ex) {
766      int _fetchesInFlight;
767      synchronized (lock) {
768        _fetchesInFlight = getFetchesInFlight();
769      }
770      if (_fetchesInFlight > 0) {
771       getLog().warn(
772           "Fetches still in progress after " +
773           TUNABLE.waitForTimerJobsSeconds + " seconds. " +
774           "fetchesInFlight=" + _fetchesInFlight);
775      } else {
776        getLog().warn(
777            "timeout waiting for timer jobs termination" +
778                " (" + TUNABLE.waitForTimerJobsSeconds + " seconds)", ex);
779        getLog().warn("Thread dump:\n" + ThreadDump.generateThredDump());
780      }
781      getLog().warn("State: " + toString());
782    } catch (Exception ex) {
783      getLog().warn("exception waiting for timer jobs termination", ex);
784    }
785    synchronized (lock) {
786      mainHashCtrl.close();
787      refreshHashCtrl.close();
788    }
789    try {
790      Future<?> _future = shutdownWaitFuture;
791      if (_future != null) {
792        _future.get();
793      }
794    } catch (Exception ex) {
795      throw new CacheException(ex);
796    }
797    Future<Void> _waitForStorage = null;
798    if (storage != null) {
799      _waitForStorage = storage.shutdown();
800    }
801    if (_waitForStorage != null) {
802      try {
803        _waitForStorage.get();
804      } catch (Exception ex) {
805        StorageAdapter.rethrow("shutdown", ex);
806      }
807    }
808    synchronized (lock) {
809      storage = null;
810    }
811    synchronized (lock) {
812      if (timer != null) {
813        timer.cancel();
814        timer = null;
815      }
816      if (refreshPool != null) {
817        refreshPool.destroy();
818        refreshPool = null;
819      }
820      mainHash = refreshHash = null;
821      source = null;
822      if (manager != null) {
823        manager.cacheDestroyed(this);
824        manager = null;
825      }
826    }
827  }
828
829  /**
830   * Complete iteration of all entries in the cache, including
831   * storage / persisted entries. The iteration may include expired
832   * entries or entries with no valid data.
833   */
834  protected ClosableIterator<Entry> iterateLocalAndStorage() {
835    if (storage == null) {
836      synchronized (lock) {
837        return iterateAllHeapEntries();
838      }
839    } else {
840      return storage.iterateAll();
841    }
842  }
843
844  @Override
845  public ClosableIterator<CacheEntry<K, T>> iterator() {
846    return new IteratorFilterEntry2Entry(iterateLocalAndStorage());
847  }
848
849  /**
850   * Filter out non valid entries and wrap each entry with a cache
851   * entry object.
852   */
853  class IteratorFilterEntry2Entry implements ClosableIterator<CacheEntry<K, T>> {
854
855    ClosableIterator<Entry> iterator;
856    Entry entry;
857
858    IteratorFilterEntry2Entry(ClosableIterator<Entry> it) { iterator = it; }
859
860    /**
861     * Between hasNext() and next() an entry may be evicted or expired.
862     * In practise we have to deliver a next entry if we return hasNext() with
863     * true, furthermore, there should be no big gap between the calls to
864     * hasNext() and next().
865     */
866    @Override
867    public boolean hasNext() {
868      while (iterator.hasNext()) {
869        entry = iterator.next();
870        if (entry.hasFreshData()) {
871          return true;
872        }
873      }
874      return false;
875    }
876
877    @Override
878    public void close() {
879      if (iterator != null) {
880        iterator.close();
881        iterator = null;
882      }
883    }
884
885    @Override
886    public CacheEntry<K, T> next() { return returnEntry(entry); }
887
888    @Override
889    public void remove() {
890      BaseCache.this.remove((K) entry.getKey());
891    }
892  }
893
894  protected static void removeFromList(final Entry e) {
895    e.prev.next = e.next;
896    e.next.prev = e.prev;
897    e.removedFromList();
898  }
899
900  protected static void insertInList(final Entry _head, final Entry e) {
901    e.prev = _head;
902    e.next = _head.next;
903    e.next.prev = e;
904    _head.next = e;
905  }
906
907  protected static final int getListEntryCount(final Entry _head) {
908    Entry e = _head.next;
909    int cnt = 0;
910    while (e != _head) {
911      cnt++;
912      if (e == null) {
913        return -cnt;
914      }
915      e = e.next;
916    }
917    return cnt;
918  }
919
920  protected static final <E extends Entry> void moveToFront(final E _head, final E e) {
921    removeFromList(e);
922    insertInList(_head, e);
923  }
924
925  protected static final <E extends Entry> E insertIntoTailCyclicList(final E _head, final E e) {
926    if (_head == null) {
927      return (E) e.shortCircuit();
928    }
929    e.next = _head;
930    e.prev = _head.prev;
931    _head.prev = e;
932    e.prev.next = e;
933    return _head;
934  }
935
936  /**
937   * Insert X into A B C, yields: A X B C.
938   */
939  protected static final <E extends Entry> E insertAfterHeadCyclicList(final E _head, final E e) {
940    if (_head == null) {
941      return (E) e.shortCircuit();
942    }
943    e.prev = _head;
944    e.next = _head.next;
945    _head.next.prev = e;
946    _head.next = e;
947    return _head;
948  }
949
950  /** Insert element at the head of the list */
951  protected static final <E extends Entry> E insertIntoHeadCyclicList(final E _head, final E e) {
952    if (_head == null) {
953      return (E) e.shortCircuit();
954    }
955    e.next = _head;
956    e.prev = _head.prev;
957    _head.prev.next = e;
958    _head.prev = e;
959    return e;
960  }
961
962  protected static <E extends Entry> E removeFromCyclicList(final E _head, E e) {
963    if (e.next == e) {
964      e.removedFromList();
965      return null;
966    }
967    Entry _eNext = e.next;
968    e.prev.next = _eNext;
969    e.next.prev = e.prev;
970    e.removedFromList();
971    return e == _head ? (E) _eNext : _head;
972  }
973
974  protected static Entry removeFromCyclicList(final Entry e) {
975    Entry _eNext = e.next;
976    e.prev.next = _eNext;
977    e.next.prev = e.prev;
978    e.removedFromList();
979    return _eNext == e ? null : _eNext;
980  }
981
982  protected static int getCyclicListEntryCount(Entry e) {
983    if (e == null) { return 0; }
984    final Entry _head = e;
985    int cnt = 0;
986    do {
987      cnt++;
988      e = e.next;
989      if (e == null) {
990        return -cnt;
991      }
992    } while (e != _head);
993    return cnt;
994  }
995
996  protected static boolean checkCyclicListIntegrity(Entry e) {
997    if (e == null) { return true; }
998    Entry _head = e;
999    do {
1000      if (e.next == null) {
1001        return false;
1002      }
1003      if (e.next.prev == null) {
1004        return false;
1005      }
1006      if (e.next.prev != e) {
1007        return false;
1008      }
1009      e = e.next;
1010    } while (e != _head);
1011    return true;
1012  }
1013
1014  /**
1015   * Record an entry hit.
1016   */
1017  protected abstract void recordHit(E e);
1018
1019  /**
1020   * New cache entry, put it in the replacement algorithm structure
1021   */
1022  protected abstract void insertIntoReplacementList(E e);
1023
1024  /**
1025   * Entry object factory. Return an entry of the proper entry subtype for
1026   * the replacement/eviction algorithm.
1027   */
1028  protected abstract E newEntry();
1029
1030
1031  /**
1032   * Find an entry that should be evicted. Called within structure lock.
1033   * After doing some checks the cache will call {@link #removeEntryFromReplacementList(Entry)}
1034   * if this entry will be really evicted. Pinned entries may be skipped. A
1035   * good eviction algorithm returns another candidate on sequential calls, even
1036   * if the candidate was not removed.
1037   *
1038   * <p/>Rationale: Within the structure lock we can check for an eviction candidate
1039   * and may remove it from the list. However, we cannot process additional operations or
1040   * events which affect the entry. For this, we need to acquire the lock on the entry
1041   * first.
1042   */
1043  protected E findEvictionCandidate() {
1044    return null;
1045  }
1046
1047
1048  /**
1049   *
1050   */
1051  protected void removeEntryFromReplacementList(E e) {
1052    removeFromList(e);
1053  }
1054
1055  /**
1056   * Check whether we have an entry in the ghost table
1057   * remove it from ghost and insert it into the replacement list.
1058   * null if nothing there. This may also do an optional eviction
1059   * if the size limit of the cache is reached, because some replacement
1060   * algorithms (ARC) do this together.
1061   */
1062  protected E checkForGhost(K key, int hc) { return null; }
1063
1064  /**
1065   * Implement unsynchronized lookup if it is supported by the eviction.
1066   * If a null is returned the lookup is redone synchronized.
1067   */
1068  protected E lookupEntryUnsynchronized(K key, int hc) { return null; }
1069
1070  @Override
1071  public T get(K key) {
1072    return returnValue(getEntryInternal(key));
1073  }
1074
1075  /**
1076   * Wrap entry in a separate object instance. We can return the entry directly, however we lock on
1077   * the entry object.
1078   */
1079  protected CacheEntry<K, T> returnEntry(final Entry<E, K, T> e) {
1080    if (e == null) {
1081      return null;
1082    }
1083    synchronized (e) {
1084      final K _key = e.getKey();
1085      final T _value = e.getValue();
1086      final Throwable _exception = e.getException();
1087      final long _lastModification = e.getLastModification();
1088      CacheEntry<K, T> ce = new CacheEntry<K, T>() {
1089        @Override
1090        public K getKey() {
1091          return _key;
1092        }
1093
1094        @Override
1095        public T getValue() {
1096          return _value;
1097        }
1098
1099        @Override
1100        public Throwable getException() {
1101          return _exception;
1102        }
1103
1104        @Override
1105        public long getLastModification() {
1106          return _lastModification;
1107        }
1108
1109        @Override
1110        public String toString() {
1111          long _expiry = e.getValueExpiryTime();
1112          return "CacheEntry(" +
1113              "key=" + getKey() + ", " +
1114              "value=" + getValue() + ", " +
1115              ((getException() != null) ? "exception=" + e.getException() + ", " : "") +
1116              "updated=" + formatMillis(getLastModification()) + ", " +
1117              "expiry=" + (_expiry != 0 ? (formatMillis(_expiry)) : "-") + ", " +
1118              "flags=" + (_expiry == 0 ? e.nextRefreshTime : "-") + ")";
1119        }
1120
1121      };
1122      return ce;
1123    }
1124  }
1125
1126  @Override
1127  public CacheEntry<K, T> getEntry(K key) {
1128    return returnEntry(getEntryInternal(key));
1129  }
1130
1131  protected E getEntryInternal(K key) {
1132    for (;;) {
1133      E e = lookupOrNewEntrySynchronized(key);
1134      if (e.hasFreshData()) { return e; }
1135      synchronized (e) {
1136        e.waitForFetch();
1137        if (e.hasFreshData()) {
1138          return e;
1139        }
1140        if (e.isRemovedState()) {
1141          continue;
1142        }
1143        e.startFetch();
1144      }
1145      boolean _finished = false;
1146      try {
1147        e.finishFetch(fetch(e));
1148        _finished = true;
1149      } finally {
1150        e.ensureFetchAbort(_finished);
1151      }
1152      evictEventually();
1153      return e;
1154    }
1155  }
1156
1157  /**
1158   * Insert the storage entry in the heap cache and return it. Used when storage entries
1159   * are queried. We need to check whether the entry is present meanwhile, get the entry lock
1160   * and maybe fetch it from the source. Doubles with {@link #getEntryInternal(Object)}
1161   * except we do not need to retrieve the data from the storage again.
1162   *
1163   * @param _needsFetch if true the entry is fetched from CacheSource when expired.
1164   * @return a cache entry is always returned, however, it may be outdated
1165   */
1166  protected Entry insertEntryFromStorage(StorageEntry se, boolean _needsFetch) {
1167    int _spinCount = TUNABLE.maximumEntryLockSpins;
1168    for (;;) {
1169      if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); }
1170      E e = lookupOrNewEntrySynchronized((K) se.getKey());
1171      if (e.hasFreshData()) { return e; }
1172      synchronized (e) {
1173        if (!e.isDataValidState()) {
1174          if (e.isRemovedState()) {
1175            continue;
1176          }
1177          if (e.isFetchInProgress()) {
1178            e.waitForFetch();
1179            return e;
1180          }
1181          e.startFetch();
1182        }
1183      }
1184      boolean _finished = false;
1185      try {
1186        e.finishFetch(insertEntryFromStorage(se, e, _needsFetch));
1187        _finished = true;
1188      } finally {
1189        e.ensureFetchAbort(_finished);
1190      }
1191      evictEventually();
1192      return e;
1193    }
1194  }
1195
1196  /**
1197   * Insert a cache entry for the given key and run action under the entry
1198   * lock. If the cache entry has fresh data, we do not run the action.
1199   * Called from storage. The entry referenced by the key is expired and
1200   * will be purged.
1201   */
1202  protected void lockAndRunForPurge(Object key, PurgeableStorage.PurgeAction _action) {
1203    int _spinCount = TUNABLE.maximumEntryLockSpins;
1204    E e;
1205    boolean _virgin;
1206    for (;;) {
1207      if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); }
1208      e = lookupOrNewEntrySynchronized((K) key);
1209      if (e.hasFreshData()) { return; }
1210      synchronized (e) {
1211        e.waitForFetch();
1212        if (e.isDataValidState()) {
1213          return;
1214        }
1215        if (e.isRemovedState()) {
1216          continue;
1217        }
1218        _virgin = e.isVirgin();
1219        e.startFetch();
1220        break;
1221      }
1222    }
1223    boolean _finished = false;
1224    try {
1225      StorageEntry se = _action.checkAndPurge(key);
1226      synchronized (e) {
1227        if (_virgin) {
1228          e.finishFetch(Entry.VIRGIN_STATE);
1229          evictEntryFromHeap(e);
1230        } else {
1231          e.finishFetch(Entry.LOADED_NON_VALID);
1232          evictEntryFromHeap(e);
1233        }
1234      }
1235      _finished = true;
1236    } finally {
1237      e.ensureFetchAbort(_finished);
1238    }
1239  }
1240
1241  protected final void evictEventually() {
1242    int _spinCount = TUNABLE.maximumEvictSpins;
1243    E _previousCandidate = null;
1244    while (evictionNeeded) {
1245      if (_spinCount-- <= 0) { return; }
1246      E e;
1247      synchronized (lock) {
1248        checkClosed();
1249        if (getLocalSize() <= maxSize) {
1250          evictionNeeded = false;
1251          return;
1252        }
1253        e = findEvictionCandidate();
1254      }
1255      boolean _shouldStore;
1256      synchronized (e) {
1257        if (e.isRemovedState()) {
1258          continue;
1259        }
1260        if (e.isPinned()) {
1261          if (e != _previousCandidate) {
1262            _previousCandidate = e;
1263            continue;
1264          } else {
1265            return;
1266          }
1267        }
1268
1269        boolean _storeEvenImmediatelyExpired = hasKeepAfterExpired() && (e.isDataValidState() || e.isExpiredState() || e.nextRefreshTime == Entry.FETCH_NEXT_TIME_STATE);
1270        _shouldStore =
1271            (storage != null) && (_storeEvenImmediatelyExpired || e.hasFreshData());
1272        if (_shouldStore) {
1273          e.startFetch();
1274        } else {
1275          evictEntryFromHeap(e);
1276        }
1277      }
1278      if (_shouldStore) {
1279        try {
1280          storage.evict(e);
1281        } finally {
1282          synchronized (e) {
1283            e.finishFetch(Entry.FETCH_ABORT);
1284            evictEntryFromHeap(e);
1285          }
1286        }
1287      }
1288    }
1289  }
1290
1291  private void evictEntryFromHeap(E e) {
1292    synchronized (lock) {
1293      if (e.isRemovedFromReplacementList()) {
1294        if (removeEntryFromHash(e)) {
1295          evictedButInHashCnt--;
1296          evictedCnt++;
1297        }
1298      } else {
1299        if (removeEntry(e)) {
1300          evictedCnt++;
1301        }
1302      }
1303      evictionNeeded = getLocalSize() > maxSize;
1304    }
1305    e.notifyAll();
1306  }
1307
1308  /**
1309   * Remove the entry from the hash and the replacement list.
1310   * There is a race condition to catch: The eviction may run
1311   * in a parallel thread and may have already selected this
1312   * entry.
1313   */
1314  protected boolean removeEntry(E e) {
1315    if (!e.isRemovedFromReplacementList()) {
1316      removeEntryFromReplacementList(e);
1317    }
1318    return removeEntryFromHash(e);
1319  }
1320
1321  /**
1322   * Return the entry, if it is in the cache, without invoking the
1323   * cache source.
1324   *
1325   * <p>The cache storage is asked whether the entry is present.
1326   * If the entry is not present, this result is cached in the local
1327   * cache.
1328   */
1329  protected E peekEntryInternal(K key) {
1330    final int hc = modifiedHash(key.hashCode());
1331    int _spinCount = TUNABLE.maximumEntryLockSpins;
1332    for (;;) {
1333      if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); }
1334      E e = lookupEntryUnsynchronized(key, hc);
1335      if (e == null) {
1336        synchronized (lock) {
1337          e = lookupEntry(key, hc);
1338          if (e == null && storage != null) {
1339            e = newEntry(key, hc);
1340          }
1341        }
1342      }
1343      if (e == null) {
1344        peekMissCnt++;
1345        return null;
1346      }
1347      if (e.hasFreshData()) { return e; }
1348      boolean _hasFreshData = false;
1349      if (storage != null) {
1350        boolean _needsLoad;
1351        synchronized (e) {
1352          e.waitForFetch();
1353          if (e.isRemovedState()) {
1354            continue;
1355          }
1356          if (e.hasFreshData()) {
1357            return e;
1358          }
1359          _needsLoad = conditionallyStartProcess(e);
1360        }
1361        if (_needsLoad) {
1362          boolean _finished = false;
1363          try {
1364            long t = fetchWithStorage(e, false);
1365            e.finishFetch(t);
1366            _hasFreshData = e.hasFreshData(System.currentTimeMillis(), t);
1367            _finished = true;
1368          } finally {
1369            e.ensureFetchAbort(_finished);
1370          }
1371        }
1372
1373      }
1374      evictEventually();
1375      if (_hasFreshData) {
1376        return e;
1377      }
1378      peekHitNotFreshCnt++;
1379      return null;
1380    }
1381  }
1382
1383  @Override
1384  public boolean contains(K key) {
1385    E e = peekEntryInternal(key);
1386    if (e != null) {
1387      return true;
1388    }
1389    return false;
1390  }
1391
1392  @Override
1393  public T peek(K key) {
1394    E e = peekEntryInternal(key);
1395    if (e != null) {
1396      return returnValue(e);
1397    }
1398    return null;
1399  }
1400
1401  @Override
1402  public CacheEntry<K, T> peekEntry(K key) {
1403    return returnEntry(peekEntryInternal(key));
1404  }
1405
1406  @Override
1407  public boolean putIfAbsent(K key, T value) {
1408    if (storage == null) {
1409       int _spinCount = TUNABLE.maximumEntryLockSpins;
1410      E e;
1411      for (;;) {
1412        if (_spinCount-- <= 0) {
1413          throw new CacheLockSpinsExceededError();
1414        }
1415        e = lookupOrNewEntrySynchronized(key);
1416        synchronized (e) {
1417          if (e.isRemovedState()) {
1418            continue;
1419          }
1420          long t = System.currentTimeMillis();
1421          if (e.hasFreshData(t)) {
1422            return false;
1423          }
1424          synchronized (lock) {
1425            peekHitNotFreshCnt++;
1426          }
1427          e.nextRefreshTime = insertOnPut(e, value, t, t);
1428          return true;
1429        }
1430      }
1431    }
1432    int _spinCount = TUNABLE.maximumEntryLockSpins;
1433    E e; long t;
1434    for (;;) {
1435      if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); }
1436      e = lookupOrNewEntrySynchronized(key);
1437      synchronized (e) {
1438        e.waitForFetch();
1439        if (e.isRemovedState()) {
1440          continue;
1441        }
1442        t = System.currentTimeMillis();
1443        if (e.hasFreshData(t)) {
1444          return false;
1445        }
1446        e.startFetch();
1447        break;
1448      }
1449    }
1450    if (e.isVirgin()) {
1451      long _result = fetchWithStorage(e, false);
1452      long now = System.currentTimeMillis();
1453      if (e.hasFreshData(now, _result)) {
1454        e.finishFetch(_result);
1455        return false;
1456      }
1457      e.nextRefreshTime = Entry.LOADED_NON_VALID_AND_PUT;
1458    }
1459    e.finishFetch(insertOnPut(e, value, t, t));
1460    evictEventually();
1461    return true;
1462  }
1463
1464  @Override
1465  public void put(K key, T value) {
1466    int _spinCount = TUNABLE.maximumEntryLockSpins;
1467    E e;
1468    for (;;) {
1469      if (_spinCount-- <= 0) { throw new CacheLockSpinsExceededError(); }
1470      e = lookupOrNewEntrySynchronized(key);
1471      synchronized (e) {
1472        if (e.isRemovedState()) {
1473          continue;
1474        }
1475        if (e.isFetchInProgress()) {
1476          e.waitForFetch();
1477          continue;
1478        } else {
1479          e.startFetch();
1480          break;
1481        }
1482      }
1483    }
1484    boolean _finished = false;
1485    try {
1486      long t = System.currentTimeMillis();
1487      e.finishFetch(insertOnPut(e, value, t, t));
1488      _finished = true;
1489    } finally {
1490      e.ensureFetchAbort(_finished);
1491    }
1492    evictEventually();
1493  }
1494
1495  /**
1496   * Remove the object mapped to a key from the cache.
1497   *
1498   * <p>Operation with storage: If there is no entry within the cache there may
1499   * be one in the storage, so we need to send the remove to the storage. However,
1500   * if a remove() and a get() is going on in parallel it may happen that the entry
1501   * gets removed from the storage and added again by the tail part of get(). To
1502   * keep the cache and the storage consistent it must be ensured that this thread
1503   * is the only one working on the entry.
1504   */
1505  public boolean removeWithFlag(K key) {
1506    if (storage == null) {
1507      E e = lookupEntrySynchronized(key);
1508      if (e != null) {
1509        synchronized (e) {
1510          e.waitForFetch();
1511          if (!e.isRemovedState()) {
1512            synchronized (lock) {
1513              boolean f = e.hasFreshData();
1514              if (removeEntry(e)) {
1515                removedCnt++;
1516                return f;
1517              }
1518              return false;
1519            }
1520          }
1521        }
1522      }
1523      return false;
1524    }
1525    int _spinCount = TUNABLE.maximumEntryLockSpins;
1526    E e;
1527    boolean _hasFreshData;
1528    for (;;) {
1529      if (_spinCount-- <= 0) {
1530        throw new CacheLockSpinsExceededError();
1531      }
1532      e = lookupOrNewEntrySynchronized(key);
1533      synchronized (e) {
1534        e.waitForFetch();
1535        if (e.isRemovedState()) {
1536          continue;
1537        }
1538        _hasFreshData = e.hasFreshData();
1539        e.startFetch();
1540        break;
1541      }
1542    }
1543    boolean _finished = false;
1544    try {
1545      if (!_hasFreshData && e.isVirgin()) {
1546        long t = fetchWithStorage(e, false);
1547        _hasFreshData = e.hasFreshData(System.currentTimeMillis(), t);
1548      }
1549      if (_hasFreshData) {
1550        storage.remove(key);
1551      }
1552      synchronized (e) {
1553        e.finishFetch(Entry.LOADED_NON_VALID);
1554        if (_hasFreshData) {
1555          synchronized (lock) {
1556            if (removeEntry(e)) {
1557              removedCnt++;
1558            }
1559          }
1560        }
1561      }
1562      _finished = true;
1563    } finally {
1564      e.ensureFetchAbort(_finished);
1565    }
1566    return _hasFreshData;
1567  }
1568
1569  @Override
1570  public void remove(K key) {
1571    removeWithFlag(key);
1572  }
1573
1574  @Override
1575  public void prefetch(final K key) {
1576    if (refreshPool == null ||
1577        lookupEntrySynchronized(key) != null) {
1578      return;
1579    }
1580    Runnable r = new Runnable() {
1581      @Override
1582      public void run() {
1583        get(key);
1584      }
1585    };
1586    refreshPool.submit(r);
1587  }
1588
1589  public void prefetch(final List<K> keys, final int _startIndex, final int _endIndexExclusive) {
1590    if (keys.size() == 0 || _startIndex == _endIndexExclusive) {
1591      return;
1592    }
1593    if (keys.size() <= _endIndexExclusive) {
1594      throw new IndexOutOfBoundsException("end > size");
1595    }
1596    if (_startIndex > _endIndexExclusive) {
1597      throw new IndexOutOfBoundsException("start > end");
1598    }
1599    if (_startIndex > 0) {
1600      throw new IndexOutOfBoundsException("end < 0");
1601    }
1602    Set<K> ks = new AbstractSet<K>() {
1603      @Override
1604      public Iterator<K> iterator() {
1605        return new Iterator<K>() {
1606          int idx = _startIndex;
1607          @Override
1608          public boolean hasNext() {
1609            return idx < _endIndexExclusive;
1610          }
1611
1612          @Override
1613          public K next() {
1614            return keys.get(idx++);
1615          }
1616
1617          @Override
1618          public void remove() {
1619            throw new UnsupportedOperationException();
1620          }
1621        };
1622      }
1623
1624      @Override
1625      public int size() {
1626        return _endIndexExclusive - _startIndex;
1627      }
1628    };
1629    prefetch(ks);
1630  }
1631
1632  @Override
1633  public void prefetch(final Set<K> keys) {
1634    if (refreshPool == null) {
1635      getAll(keys);
1636      return;
1637    }
1638    boolean _complete = true;
1639    for (K k : keys) {
1640      if (lookupEntryUnsynchronized(k, modifiedHash(k.hashCode())) == null) {
1641        _complete = false; break;
1642      }
1643    }
1644    if (_complete) {
1645      return;
1646    }
1647    Runnable r = new Runnable() {
1648      @Override
1649      public void run() {
1650        getAll(keys);
1651      }
1652    };
1653    refreshPool.submit(r);
1654  }
1655
1656  /**
1657   * Lookup or create a new entry. The new entry is created, because we need
1658   * it for locking within the data fetch.
1659   */
1660  protected E lookupOrNewEntrySynchronized(K key) {
1661    int hc = modifiedHash(key.hashCode());
1662    E e = lookupEntryUnsynchronized(key, hc);
1663    if (e == null) {
1664      synchronized (lock) {
1665        checkClosed();
1666        e = lookupEntry(key, hc);
1667        if (e == null) {
1668          e = newEntry(key, hc);
1669        }
1670      }
1671    }
1672    return e;
1673  }
1674
1675  protected T returnValue(Entry<E, K,T> e) {
1676    T v = e.value;
1677    if (v instanceof ExceptionWrapper) {
1678      ExceptionWrapper w = (ExceptionWrapper) v;
1679      if (w.additionalExceptionMessage == null) {
1680        synchronized (e) {
1681          long t = e.getValueExpiryTime();
1682          w.additionalExceptionMessage = "(expiry=" + (t > 0 ? formatMillis(t) : "none") + ") " + w.getException();
1683        }
1684      }
1685      throw new PropagatedCacheException(w.additionalExceptionMessage, w.getException());
1686    }
1687    return v;
1688  }
1689
1690  protected E lookupEntrySynchronized(K key) {
1691    int hc = modifiedHash(key.hashCode());
1692    E e = lookupEntryUnsynchronized(key, hc);
1693    if (e == null) {
1694      synchronized (lock) {
1695        e = lookupEntry(key, hc);
1696      }
1697    }
1698    return e;
1699  }
1700
1701  protected E lookupEntry(K key, int hc) {
1702    E e = Hash.lookup(mainHash, key, hc);
1703    if (e != null) {
1704      recordHit(e);
1705      return e;
1706    }
1707    e = refreshHashCtrl.remove(refreshHash, key, hc);
1708    if (e != null) {
1709      refreshHitCnt++;
1710      mainHash = mainHashCtrl.insert(mainHash, e);
1711      recordHit(e);
1712      return e;
1713    }
1714    return null;
1715  }
1716
1717
1718  /**
1719   * Insert new entry in all structures (hash and replacement list). May evict an
1720   * entry if the maximum capacity is reached.
1721   */
1722  protected E newEntry(K key, int hc) {
1723    if (getLocalSize() >= maxSize) {
1724      evictionNeeded = true;
1725    }
1726    E e = checkForGhost(key, hc);
1727    if (e == null) {
1728      e = newEntry();
1729      e.key = key;
1730      e.hashCode = hc;
1731      insertIntoReplacementList(e);
1732    }
1733    mainHash = mainHashCtrl.insert(mainHash, e);
1734    newEntryCnt++;
1735    return e;
1736  }
1737
1738  /**
1739   * Called when expiry of an entry happens. Remove it from the
1740   * main cache, refresh cache and from the (lru) list. Also cancel the timer.
1741   * Called under big lock.
1742   */
1743
1744  /**
1745   * The entry is already removed from the replacement list. stop/reset timer, if needed.
1746   * Called under big lock.
1747   */
1748  private boolean removeEntryFromHash(E e) {
1749    boolean f = mainHashCtrl.remove(mainHash, e) || refreshHashCtrl.remove(refreshHash, e);
1750    checkForHashCodeChange(e);
1751    cancelExpiryTimer(e);
1752    if (e.isVirgin()) {
1753      virginEvictCnt++;
1754    }
1755    e.setRemovedState();
1756    return f;
1757  }
1758
1759  private void cancelExpiryTimer(Entry e) {
1760    if (e.task != null) {
1761      e.task.cancel();
1762      timerCancelCount++;
1763      if (timerCancelCount >= 10000) {
1764        timer.purge();
1765        timerCancelCount = 0;
1766      }
1767      e.task = null;
1768    }
1769  }
1770
1771  /**
1772   * Check whether the key was modified during the stay of the entry in the cache.
1773   * We only need to check this when the entry is removed, since we expect that if
1774   * the key has changed, the stored hash code in the cache will not match any more and
1775   * the item is evicted very fast.
1776   */
1777  private void checkForHashCodeChange(Entry e) {
1778    if (modifiedHash(e.key.hashCode()) != e.hashCode && !e.isStale()) {
1779      if (keyMutationCount ==  0) {
1780        getLog().warn("Key mismatch! Key hashcode changed! keyClass=" + e.key.getClass().getName());
1781        String s;
1782        try {
1783          s = e.key.toString();
1784          if (s != null) {
1785            getLog().warn("Key mismatch! key.toString(): " + s);
1786          }
1787        } catch (Throwable t) {
1788          getLog().warn("Key mismatch! key.toString() threw exception", t);
1789        }
1790      }
1791      keyMutationCount++;
1792    }
1793  }
1794
1795  /**
1796   * Time when the element should be fetched again from the underlying storage.
1797   * If 0 then the object should not be cached at all. -1 means no expiry.
1798   *
1799   * @param _newObject might be a fetched value or an exception wrapped into the {@link ExceptionWrapper}
1800   */
1801  static <K, T>  long calcNextRefreshTime(
1802      K _key, T _newObject, long now, Entry _entry,
1803      EntryExpiryCalculator<K, T> ec, long _maxLinger,
1804      ExceptionExpiryCalculator<K> _exceptionEc, long _exceptionMaxLinger) {
1805    if (!(_newObject instanceof ExceptionWrapper)) {
1806      if (_maxLinger == 0) {
1807        return 0;
1808      }
1809      if (ec != null) {
1810        long t = ec.calculateExpiryTime(_key, _newObject, now, _entry);
1811        return limitExpiryToMaxLinger(now, _maxLinger, t);
1812      }
1813      if (_maxLinger > 0) {
1814        return _maxLinger + now;
1815      }
1816      return -1;
1817    }
1818    if (_exceptionMaxLinger == 0) {
1819      return 0;
1820    }
1821    if (_exceptionEc != null) {
1822      long t = _exceptionEc.calculateExpiryTime(_key, ((ExceptionWrapper) _newObject).getException(), now);
1823      return limitExpiryToMaxLinger(now, _exceptionMaxLinger, t);
1824    }
1825    if (_exceptionMaxLinger > 0) {
1826      return _exceptionMaxLinger + now;
1827    } else {
1828      return _exceptionMaxLinger;
1829    }
1830  }
1831
1832  static long limitExpiryToMaxLinger(long now, long _maxLinger, long t) {
1833    if (_maxLinger > 0) {
1834      long _tMaximum = _maxLinger + now;
1835      if (t > _tMaximum) {
1836        return _tMaximum;
1837      }
1838      if (t < -1 && -t > _tMaximum) {
1839        return -_tMaximum;
1840      }
1841    }
1842    return t;
1843  }
1844
1845  protected long calcNextRefreshTime(K _key, T _newObject, long now, Entry _entry) {
1846    return calcNextRefreshTime(
1847        _key, _newObject, now, _entry,
1848        entryExpiryCalculator, maxLinger,
1849        exceptionExpiryCalculator, exceptionMaxLinger);
1850  }
1851
1852  protected long fetch(final E e) {
1853    if (storage != null) {
1854      return fetchWithStorage(e, true);
1855    } else {
1856      return fetchFromSource(e);
1857    }
1858  }
1859
1860  protected boolean conditionallyStartProcess(E e) {
1861    if (!e.isVirgin()) {
1862      return false;
1863    }
1864    e.startFetch();
1865    return true;
1866  }
1867
1868  /**
1869   *
1870   * @param e
1871   * @param _needsFetch true if value needs to be fetched from the cache source.
1872   *                   This is false, when the we only need to peek for an value already mapped.
1873   */
1874  protected long fetchWithStorage(E e, boolean _needsFetch) {
1875    if (!e.isVirgin()) {
1876      if (_needsFetch) {
1877        return fetchFromSource(e);
1878      }
1879      return Entry.LOADED_NON_VALID;
1880    }
1881    StorageEntry se = storage.get(e.key);
1882    if (se == null) {
1883      if (_needsFetch) {
1884        synchronized (lock) {
1885          loadMissCnt++;
1886        }
1887        return fetchFromSource(e);
1888      }
1889      synchronized (lock) {
1890        touchedTime = System.currentTimeMillis();
1891        loadNonFreshCnt++;
1892      }
1893      return Entry.LOADED_NON_VALID;
1894    }
1895    return insertEntryFromStorage(se, e, _needsFetch);
1896  }
1897
1898  protected long insertEntryFromStorage(StorageEntry se, E e, boolean _needsFetch) {
1899    e.setLastModificationFromStorage(se.getCreatedOrUpdated());
1900    long now = System.currentTimeMillis();
1901    T v = (T) se.getValueOrException();
1902    long _nextRefreshTime = maxLinger == 0 ? 0 : Long.MAX_VALUE;
1903    long _expiryTimeFromStorage = se.getValueExpiryTime();
1904    boolean _expired = _expiryTimeFromStorage != 0 && _expiryTimeFromStorage <= now;
1905    if (!_expired && timer != null) {
1906      _nextRefreshTime = calcNextRefreshTime((K) se.getKey(), v, se.getCreatedOrUpdated(), null);
1907      _expired = _nextRefreshTime > Entry.EXPIRY_TIME_MIN && _nextRefreshTime <= now;
1908    }
1909    boolean _fetchAlways = timer == null && maxLinger == 0;
1910    if (_expired || _fetchAlways) {
1911      if (_needsFetch) {
1912        e.value = se.getValueOrException();
1913        e.setLoadedNonValidAndFetch();
1914        return fetchFromSource(e);
1915      } else {
1916        synchronized (lock) {
1917          touchedTime = now;
1918          loadNonFreshCnt++;
1919        }
1920        return Entry.LOADED_NON_VALID;
1921      }
1922    }
1923    return insert(e, (T) se.getValueOrException(), 0, now, INSERT_STAT_UPDATE, _nextRefreshTime);
1924  }
1925
1926  protected long fetchFromSource(E e) {
1927    T v;
1928    long t0 = System.currentTimeMillis();
1929    try {
1930      if (source == null) {
1931        throw new CacheUsageExcpetion("source not set");
1932      }
1933      if (e.isVirgin() || e.hasException()) {
1934        v = source.get((K) e.key, t0, null, e.getLastModification());
1935      } else {
1936        v = source.get((K) e.key, t0, (T) e.getValue(), e.getLastModification());
1937      }
1938      e.setLastModification(t0);
1939    } catch (Throwable _ouch) {
1940      v = (T) new ExceptionWrapper(_ouch);
1941    }
1942    long t = System.currentTimeMillis();
1943    return insertFetched(e, v, t0, t);
1944  }
1945
1946  protected final long insertFetched(E e, T v, long t0, long t) {
1947    return insert(e, v, t0, t, INSERT_STAT_UPDATE);
1948  }
1949
1950  protected final long insertOnPut(E e, T v, long t0, long t) {
1951    e.setLastModification(t0);
1952    return insert(e, v, t0, t, INSERT_STAT_PUT);
1953  }
1954
1955  /**
1956   * Calculate the next refresh time if a timer / expiry is needed and call insert.
1957   */
1958  protected final long insert(E e, T v, long t0, long t, byte _updateStatistics) {
1959    long _nextRefreshTime = maxLinger == 0 ? 0 : Long.MAX_VALUE;
1960    if (timer != null) {
1961      if (e.isVirgin() || e.hasException()) {
1962        _nextRefreshTime = calcNextRefreshTime((K) e.getKey(), v, t0, null);
1963      } else {
1964        _nextRefreshTime = calcNextRefreshTime((K) e.getKey(), v, t0, e);
1965      }
1966    }
1967    return insert(e,v,t0,t, _updateStatistics, _nextRefreshTime);
1968  }
1969
1970  final static byte INSERT_STAT_NO_UPDATE = 0;
1971  final static byte INSERT_STAT_UPDATE = 1;
1972  final static byte INSERT_STAT_PUT = 2;
1973
1974  /**
1975   * @param _nextRefreshTime -1/MAXVAL: eternal, 0: expires immediately
1976   */
1977  protected final long insert(E e, T _value, long t0, long t, byte _updateStatistics, long _nextRefreshTime) {
1978    if (_nextRefreshTime == -1) {
1979      _nextRefreshTime = Long.MAX_VALUE;
1980    }
1981    boolean _suppressException =
1982      _value instanceof ExceptionWrapper && hasSuppressExceptions() && e.getValue() != Entry.INITIAL_VALUE && !e.hasException();
1983    if (!_suppressException) {
1984      e.value = _value;
1985    }
1986
1987    CacheStorageException _storageException = null;
1988    if (storage != null && e.isDirty() && (_nextRefreshTime != 0 || hasKeepAfterExpired())) {
1989      try {
1990        storage.put(e, _nextRefreshTime);
1991      } catch (CacheStorageException ex) {
1992        _storageException = ex;
1993      } catch (Throwable ex) {
1994        _storageException = new CacheStorageException(ex);
1995      }
1996    }
1997
1998    long _nextRefreshTimeWithState;
1999    synchronized (lock) {
2000      checkClosed();
2001      touchedTime = t;
2002      if (_updateStatistics == INSERT_STAT_UPDATE) {
2003        if (t0 == 0) {
2004          loadHitCnt++;
2005        } else {
2006          if (_suppressException) {
2007            suppressedExceptionCnt++;
2008            fetchExceptionCnt++;
2009          } else {
2010            if (_value instanceof ExceptionWrapper) {
2011              Log log = getLog();
2012              if (log.isDebugEnabled()) {
2013                log.debug(
2014                    "caught exception, expires at: " + formatMillis(_nextRefreshTime),
2015                    ((ExceptionWrapper) _value).getException());
2016              }
2017              fetchExceptionCnt++;
2018            }
2019          }
2020          fetchCnt++;
2021          fetchMillis += t - t0;
2022          if (e.isGettingRefresh()) {
2023            refreshCnt++;
2024          }
2025          if (e.isLoadedNonValidAndFetch()) {
2026            loadNonFreshAndFetchedCnt++;
2027          } else if (!e.isVirgin()) {
2028            fetchButHitCnt++;
2029          }
2030        }
2031      } else if (_updateStatistics == INSERT_STAT_PUT) {
2032        putCnt++;
2033        if (e.isVirgin()) {
2034          putNewEntryCnt++;
2035        }
2036        if (e.nextRefreshTime == Entry.LOADED_NON_VALID_AND_PUT) {
2037          peekHitNotFreshCnt++;
2038        }
2039      }
2040      if (_storageException != null) {
2041        throw _storageException;
2042      }
2043      _nextRefreshTimeWithState = stopStartTimer(_nextRefreshTime, e, t);
2044    } // synchronized (lock)
2045
2046    return _nextRefreshTimeWithState;
2047  }
2048
2049  protected long stopStartTimer(long _nextRefreshTime, E e, long now) {
2050    if (e.task != null) {
2051      e.task.cancel();
2052    }
2053    if (hasSharpTimeout() && _nextRefreshTime > Entry.EXPIRY_TIME_MIN) {
2054      _nextRefreshTime = -_nextRefreshTime;
2055    }
2056    if (timer != null &&
2057      (_nextRefreshTime > Entry.EXPIRY_TIME_MIN || _nextRefreshTime < -1)) {
2058      if (_nextRefreshTime < -1) {
2059        long _timerTime =
2060          -_nextRefreshTime - TUNABLE.sharpExpirySafetyGapMillis;
2061        if (_timerTime >= now) {
2062          MyTimerTask tt = new MyTimerTask();
2063          tt.entry = e;
2064          timer.schedule(tt, new Date(_timerTime));
2065          e.task = tt;
2066          _nextRefreshTime = -_nextRefreshTime;
2067        }
2068      } else {
2069        MyTimerTask tt = new MyTimerTask();
2070        tt.entry = e;
2071        timer.schedule(tt, new Date(_nextRefreshTime));
2072        e.task = tt;
2073      }
2074    } else {
2075      _nextRefreshTime = _nextRefreshTime == Long.MAX_VALUE ? Entry.FETCHED_STATE : Entry.FETCH_NEXT_TIME_STATE;
2076    }
2077    return _nextRefreshTime;
2078  }
2079
2080  /**
2081   * When the time has come remove the entry from the cache.
2082   */
2083  protected void timerEvent(final E e, long _executionTime) {
2084    if (e.isRemovedFromReplacementList()) {
2085      return;
2086    }
2087    if (refreshPool != null) {
2088      synchronized (lock) {
2089        if (isClosed()) { return; }
2090        if (e.task == null) {
2091          return;
2092        }
2093        if (e.isRemovedFromReplacementList()) {
2094          return;
2095        }
2096        if (mainHashCtrl.remove(mainHash, e)) {
2097          refreshHash = refreshHashCtrl.insert(refreshHash, e);
2098          if (e.hashCode != modifiedHash(e.key.hashCode())) {
2099            synchronized (lock) {
2100              synchronized (e) {
2101                if (!e.isRemovedState() && removeEntryFromHash(e)) {
2102                  expiredRemoveCnt++;
2103                }
2104              }
2105            }
2106            return;
2107          }
2108          Runnable r = new Runnable() {
2109            @Override
2110            public void run() {
2111              synchronized (e) {
2112                if (e.isRemovedFromReplacementList() || e.isRemovedState() || e.isFetchInProgress()) {
2113                  return;
2114                }
2115                e.setGettingRefresh();
2116              }
2117              try {
2118                long t = fetch(e);
2119                e.finishFetch(t);
2120              } catch (CacheClosedException ignore) {
2121              } catch (Throwable ex) {
2122                e.ensureFetchAbort(false);
2123                synchronized (lock) {
2124                  internalExceptionCnt++;
2125                }
2126                getLog().warn("Refresh exception", ex);
2127                try {
2128                  expireEntry(e);
2129                } catch (CacheClosedException ignore) { }
2130              }
2131             }
2132          };
2133          boolean _submitOkay = refreshPool.submit(r);
2134          if (_submitOkay) {
2135            return;
2136          }
2137          refreshSubmitFailedCnt++;
2138        }
2139
2140      }
2141
2142    } else {
2143      if (_executionTime < e.nextRefreshTime) {
2144        synchronized (e) {
2145          if (!e.isRemovedState()) {
2146            long t = System.currentTimeMillis();
2147            if (t < e.nextRefreshTime) {
2148              e.nextRefreshTime = -e.nextRefreshTime;
2149              return;
2150            } else {
2151              try {
2152                expireEntry(e);
2153              } catch (CacheClosedException ignore) { }
2154            }
2155          }
2156        }
2157        return;
2158      }
2159    }
2160    synchronized (e) {
2161      long t = System.currentTimeMillis();
2162      if (t >= e.nextRefreshTime) {
2163        try {
2164          expireEntry(e);
2165        } catch (CacheClosedException ignore) { }
2166      }
2167    }
2168  }
2169
2170  protected void expireEntry(E e) {
2171    synchronized (e) {
2172      if (e.isRemovedState() || e.isExpiredState()) {
2173        return;
2174      }
2175      if (e.isFetchInProgress()) {
2176        e.nextRefreshTime = Entry.FETCH_IN_PROGRESS_NON_VALID;
2177        return;
2178      }
2179      e.setExpiredState();
2180      synchronized (lock) {
2181        checkClosed();
2182        if (hasKeepAfterExpired()) {
2183          expiredKeptCnt++;
2184        } else {
2185          if (removeEntry(e)) {
2186            expiredRemoveCnt++;
2187          }
2188        }
2189      }
2190    }
2191  }
2192
2193  /**
2194   * Returns all cache entries within the heap cache. Entries that
2195   * are expired or contain no valid data are not filtered out.
2196   */
2197  final protected ClosableConcurrentHashEntryIterator<Entry> iterateAllHeapEntries() {
2198    return
2199      new ClosableConcurrentHashEntryIterator(
2200        mainHashCtrl, mainHash, refreshHashCtrl, refreshHash);
2201  }
2202
2203  @Override
2204  public void removeAllAtOnce(Set<K> _keys) {
2205  }
2206
2207
2208  /** JSR107 convenience getAll from array */
2209  public Map<K, T> getAll(K[] _keys) {
2210    return getAll(new HashSet<K>(Arrays.asList(_keys)));
2211  }
2212
2213  /**
2214   * JSR107 bulk interface
2215   */
2216  public Map<K, T> getAll(final Set<? extends K> _keys) {
2217    K[] ka = (K[]) new Object[_keys.size()];
2218    int i = 0;
2219    for (K k : _keys) {
2220      ka[i++] = k;
2221    }
2222    T[] va = (T[]) new Object[ka.length];
2223    getBulk(ka, va, new BitSet(), 0, ka.length);
2224    return new AbstractMap<K, T>() {
2225      @Override
2226      public T get(Object key) {
2227        if (containsKey(key)) {
2228          return BaseCache.this.get((K) key);
2229        }
2230        return null;
2231      }
2232
2233      @Override
2234      public boolean containsKey(Object key) {
2235        return _keys.contains(key);
2236      }
2237
2238      @Override
2239      public Set<Entry<K, T>> entrySet() {
2240        return new AbstractSet<Entry<K, T>>() {
2241          @Override
2242          public Iterator<Entry<K, T>> iterator() {
2243            return new Iterator<Entry<K, T>>() {
2244              Iterator<? extends K> it = _keys.iterator();
2245              @Override
2246              public boolean hasNext() {
2247                return it.hasNext();
2248              }
2249
2250              @Override
2251              public Entry<K, T> next() {
2252                final K k = it.next();
2253                final T t = BaseCache.this.get(k);
2254                return new Entry<K, T>() {
2255                  @Override
2256                  public K getKey() {
2257                    return k;
2258                  }
2259
2260                  @Override
2261                  public T getValue() {
2262                    return t;
2263                  }
2264
2265                  @Override
2266                  public T setValue(T value) {
2267                    throw new UnsupportedOperationException();
2268                  }
2269                };
2270              }
2271
2272              @Override
2273              public void remove() {
2274                throw new UnsupportedOperationException();
2275              }
2276            };
2277          }
2278
2279          @Override
2280          public int size() {
2281            return _keys.size();
2282          }
2283        };
2284      }
2285    };
2286  }
2287
2288  /**
2289   * Retrieve
2290   */
2291  @SuppressWarnings("unused")
2292  public void getBulk(K[] _keys, T[] _result, BitSet _fetched, int s, int e) {
2293    sequentialGetFallBack(_keys, _result, _fetched, s, e);
2294  }
2295
2296
2297  final void sequentialFetch(E[] ea, K[] _keys, T[] _result, BitSet _fetched, int s, int end) {
2298    for (int i = s; i < end; i++) {
2299      E e = ea[i];
2300      if (e == null) { continue; }
2301      if (!e.isDataValidState()) {
2302        synchronized (e) {
2303          if (!e.isDataValidState()) {
2304            fetch(e);
2305          }
2306          _result[i] = (T) e.getValue();
2307        }
2308      }
2309    }
2310  }
2311
2312  final void sequentialGetFallBack(K[] _keys, T[] _result, BitSet _fetched, int s, int e) {
2313    for (int i = s; i < e; i++) {
2314      if (!_fetched.get(i)) {
2315        try {
2316          _result[i] = get(_keys[i]);
2317        } catch (Exception ignore) {
2318        }
2319      }
2320    }
2321  }
2322
2323  public abstract long getHitCnt();
2324
2325  protected final int calculateHashEntryCount() {
2326    return Hash.calcEntryCount(mainHash) + Hash.calcEntryCount(refreshHash);
2327  }
2328
2329  protected final int getLocalSize() {
2330    return mainHashCtrl.size + refreshHashCtrl.size;
2331  }
2332
2333  public final int getTotalEntryCount() {
2334    synchronized (lock) {
2335      if (storage != null) {
2336        return storage.getTotalEntryCount();
2337      }
2338      return getLocalSize();
2339    }
2340  }
2341
2342  public long getExpiredCnt() {
2343    return expiredRemoveCnt + expiredKeptCnt;
2344  }
2345
2346  /**
2347   * For peek no fetch is counted if there is a storage miss, hence the extra counter.
2348   */
2349  public long getFetchesBecauseOfNewEntries() {
2350    return fetchCnt - fetchButHitCnt;
2351  }
2352
2353  protected int getFetchesInFlight() {
2354    long _fetchesBecauseOfNoEntries = getFetchesBecauseOfNewEntries();
2355    return (int) (newEntryCnt - putNewEntryCnt - virginEvictCnt
2356        - loadNonFreshCnt
2357        - loadHitCnt
2358        - _fetchesBecauseOfNoEntries);
2359  }
2360
2361  protected IntegrityState getIntegrityState() {
2362    synchronized (lock) {
2363      return new IntegrityState()
2364        .checkEquals(
2365            "newEntryCnt - virginEvictCnt == " +
2366                "getFetchesBecauseOfNewEntries() + getFetchesInFlight() + putNewEntryCnt + loadNonFreshCnt + loadHitCnt",
2367            newEntryCnt - virginEvictCnt,
2368            getFetchesBecauseOfNewEntries() + getFetchesInFlight() + putNewEntryCnt + loadNonFreshCnt + loadHitCnt)
2369        .checkLessOrEquals("getFetchesInFlight() <= 100", getFetchesInFlight(), 100)
2370        .checkEquals("newEntryCnt == getSize() + evictedCnt + expiredRemoveCnt + removeCnt", newEntryCnt, getLocalSize() + evictedCnt + expiredRemoveCnt + removedCnt)
2371        .checkEquals("newEntryCnt == getSize() + evictedCnt + getExpiredCnt() - expiredKeptCnt + removeCnt", newEntryCnt, getLocalSize() + evictedCnt + getExpiredCnt() - expiredKeptCnt + removedCnt)
2372        .checkEquals("mainHashCtrl.size == Hash.calcEntryCount(mainHash)", mainHashCtrl.size, Hash.calcEntryCount(mainHash))
2373        .checkEquals("refreshHashCtrl.size == Hash.calcEntryCount(refreshHash)", refreshHashCtrl.size, Hash.calcEntryCount(refreshHash))
2374        .check("!!evictionNeeded | (getSize() <= maxSize)", !!evictionNeeded | (getLocalSize() <= maxSize))
2375        .check("storage => storage.getAlert() < 2", storage == null || storage.getAlert() < 2);
2376    }
2377  }
2378
2379  /** Check internal data structures and throw and exception if something is wrong, used for unit testing */
2380  public final void checkIntegrity() {
2381    synchronized (lock) {
2382      IntegrityState is = getIntegrityState();
2383      if (is.getStateFlags() > 0) {
2384        throw new CacheIntegrityError(is.getStateDescriptor(), is.getFailingChecks(), toString());
2385      }
2386    }
2387  }
2388
2389
2390  public final Info getInfo() {
2391    synchronized (lock) {
2392      long t = System.currentTimeMillis();
2393      if (info != null &&
2394          (info.creationTime + info.creationDeltaMs * 17 + 333 > t)) {
2395        return info;
2396      }
2397      info = getLatestInfo(t);
2398    }
2399    return info;
2400  }
2401
2402  public final Info getLatestInfo() {
2403    return getLatestInfo(System.currentTimeMillis());
2404  }
2405
2406  private Info getLatestInfo(long t) {
2407    synchronized (lock) {
2408      info = new Info();
2409      info.creationTime = t;
2410      info.creationDeltaMs = (int) (System.currentTimeMillis() - t);
2411      return info;
2412    }
2413  }
2414
2415  protected String getExtraStatistics() { return ""; }
2416
2417  static String timestampToString(long t) {
2418    if (t == 0) {
2419      return "-";
2420    }
2421    return formatMillis(t);
2422  }
2423
2424  /**
2425   * Return status information. The status collection is time consuming, so this
2426   * is an expensive operation.
2427   */
2428  @Override
2429  public String toString() {
2430    synchronized (lock) {
2431      Info fo = getLatestInfo();
2432      return "Cache{" + name + "}"
2433        + "("
2434        + "size=" + fo.getSize() + ", "
2435        + "maxSize=" + fo.getMaxSize() + ", "
2436        + "usageCnt=" + fo.getUsageCnt() + ", "
2437        + "missCnt=" + fo.getMissCnt() + ", "
2438        + "fetchCnt=" + fo.getFetchCnt() + ", "
2439        + "fetchButHitCnt=" + fetchButHitCnt + ", "
2440        + "heapHitCnt=" + fo.hitCnt + ", "
2441        + "virginEvictCnt=" + virginEvictCnt + ", "
2442        + "fetchesInFlightCnt=" + fo.getFetchesInFlightCnt() + ", "
2443        + "newEntryCnt=" + fo.getNewEntryCnt() + ", "
2444        + "bulkGetCnt=" + fo.getBulkGetCnt() + ", "
2445        + "refreshCnt=" + fo.getRefreshCnt() + ", "
2446        + "refreshSubmitFailedCnt=" + fo.getRefreshSubmitFailedCnt() + ", "
2447        + "refreshHitCnt=" + fo.getRefreshHitCnt() + ", "
2448        + "putCnt=" + fo.getPutCnt() + ", "
2449        + "putNewEntryCnt=" + fo.getPutNewEntryCnt() + ", "
2450        + "expiredCnt=" + fo.getExpiredCnt() + ", "
2451        + "evictedCnt=" + fo.getEvictedCnt() + ", "
2452        + "removedCnt=" + fo.getRemovedCnt() + ", "
2453        + "storageLoadCnt=" + fo.getStorageLoadCnt() + ", "
2454        + "storageMissCnt=" + fo.getStorageMissCnt() + ", "
2455        + "storageHitCnt=" + fo.getStorageHitCnt() + ", "
2456        + "hitRate=" + fo.getDataHitString() + ", "
2457        + "collisionCnt=" + fo.getCollisionCnt() + ", "
2458        + "collisionSlotCnt=" + fo.getCollisionSlotCnt() + ", "
2459        + "longestCollisionSize=" + fo.getLongestCollisionSize() + ", "
2460        + "hashQuality=" + fo.getHashQualityInteger() + ", "
2461        + "msecs/fetch=" + (fo.getMillisPerFetch() >= 0 ? fo.getMillisPerFetch() : "-")  + ", "
2462        + "created=" + timestampToString(fo.getStarted()) + ", "
2463        + "cleared=" + timestampToString(fo.getCleared()) + ", "
2464        + "touched=" + timestampToString(fo.getTouched()) + ", "
2465        + "fetchExceptionCnt=" + fo.getFetchExceptionCnt() + ", "
2466        + "suppressedExceptionCnt=" + fo.getSuppressedExceptionCnt() + ", "
2467        + "internalExceptionCnt=" + fo.getInternalExceptionCnt() + ", "
2468        + "keyMutationCnt=" + fo.getKeyMutationCnt() + ", "
2469        + "infoCreated=" + timestampToString(fo.getInfoCreated()) + ", "
2470        + "infoCreationDeltaMs=" + fo.getInfoCreationDeltaMs() + ", "
2471        + "impl=\"" + getClass().getSimpleName() + "\""
2472        + getExtraStatistics() + ", "
2473        + "integrityState=" + fo.getIntegrityDescriptor() + ")";
2474    }
2475  }
2476
2477  /**
2478   * Stable interface to request information from the cache, the object
2479   * safes values that need a longer calculation time, other values are
2480   * requested directly.
2481   */
2482  public class Info {
2483
2484    int size = BaseCache.this.getLocalSize();
2485    long creationTime;
2486    int creationDeltaMs;
2487    long missCnt = fetchCnt - refreshCnt + peekHitNotFreshCnt + peekMissCnt;
2488    long storageMissCnt = loadMissCnt + loadNonFreshCnt + loadNonFreshAndFetchedCnt;
2489    long storageLoadCnt = storageMissCnt + loadHitCnt;
2490    long newEntryCnt = BaseCache.this.newEntryCnt - virginEvictCnt;
2491    long hitCnt = getHitCnt();
2492    long usageCnt =
2493        hitCnt + newEntryCnt + peekMissCnt;
2494    CollisionInfo collisionInfo;
2495    String extraStatistics;
2496    int fetchesInFlight = BaseCache.this.getFetchesInFlight();
2497
2498    {
2499      collisionInfo = new CollisionInfo();
2500      Hash.calcHashCollisionInfo(collisionInfo, mainHash);
2501      Hash.calcHashCollisionInfo(collisionInfo, refreshHash);
2502      extraStatistics = BaseCache.this.getExtraStatistics();
2503      if (extraStatistics.startsWith(", ")) {
2504        extraStatistics = extraStatistics.substring(2);
2505      }
2506    }
2507
2508    IntegrityState integrityState = getIntegrityState();
2509
2510    String percentString(double d) {
2511      String s = Double.toString(d);
2512      return (s.length() > 5 ? s.substring(0, 5) : s) + "%";
2513    }
2514
2515    public String getName() { return name; }
2516    public String getImplementation() { return BaseCache.this.getClass().getSimpleName(); }
2517    public int getSize() { return size; }
2518    public int getMaxSize() { return maxSize; }
2519    public long getStorageHitCnt() { return loadHitCnt; }
2520    public long getStorageLoadCnt() { return storageLoadCnt; }
2521    public long getStorageMissCnt() { return storageMissCnt; }
2522    public long getReadUsageCnt() { return usageCnt - putCnt; }
2523    public long getUsageCnt() { return usageCnt; }
2524    public long getMissCnt() { return missCnt; }
2525    public long getNewEntryCnt() { return newEntryCnt; }
2526    public long getFetchCnt() { return fetchCnt; }
2527    public int getFetchesInFlightCnt() { return fetchesInFlight; }
2528    public long getBulkGetCnt() { return bulkGetCnt; }
2529    public long getRefreshCnt() { return refreshCnt; }
2530    public long getInternalExceptionCnt() { return internalExceptionCnt; }
2531    public long getRefreshSubmitFailedCnt() { return refreshSubmitFailedCnt; }
2532    public long getSuppressedExceptionCnt() { return suppressedExceptionCnt; }
2533    public long getFetchExceptionCnt() { return fetchExceptionCnt; }
2534    public long getRefreshHitCnt() { return refreshHitCnt; }
2535    public long getExpiredCnt() { return BaseCache.this.getExpiredCnt(); }
2536    public long getEvictedCnt() { return evictedCnt - virginEvictCnt; }
2537    public long getRemovedCnt() { return BaseCache.this.removedCnt; }
2538    public long getPutNewEntryCnt() { return putNewEntryCnt; }
2539    public long getPutCnt() { return putCnt; }
2540    public long getKeyMutationCnt() { return keyMutationCount; }
2541    public double getDataHitRate() {
2542      long cnt = getReadUsageCnt();
2543      return cnt == 0 ? 100 : (cnt - missCnt) * 100D / cnt;
2544    }
2545    public String getDataHitString() { return percentString(getDataHitRate()); }
2546    public double getEntryHitRate() { return usageCnt == 0 ? 100 : (usageCnt - newEntryCnt + putCnt) * 100D / usageCnt; }
2547    public String getEntryHitString() { return percentString(getEntryHitRate()); }
2548    /** How many items will be accessed with collision */
2549    public int getCollisionPercentage() {
2550      return
2551        (size - collisionInfo.collisionCnt) * 100 / size;
2552    }
2553    /** 100 means each collision has its own slot */
2554    public int getSlotsPercentage() {
2555      return collisionInfo.collisionSlotCnt * 100 / collisionInfo.collisionCnt;
2556    }
2557    public int getHq0() {
2558      return Math.max(0, 105 - collisionInfo.longestCollisionSize * 5) ;
2559    }
2560    public int getHq1() {
2561      final int _metricPercentageBase = 60;
2562      int m =
2563        getCollisionPercentage() * ( 100 - _metricPercentageBase) / 100 + _metricPercentageBase;
2564      m = Math.min(100, m);
2565      m = Math.max(0, m);
2566      return m;
2567    }
2568    public int getHq2() {
2569      final int _metricPercentageBase = 80;
2570      int m =
2571        getSlotsPercentage() * ( 100 - _metricPercentageBase) / 100 + _metricPercentageBase;
2572      m = Math.min(100, m);
2573      m = Math.max(0, m);
2574      return m;
2575    }
2576    public int getHashQualityInteger() {
2577      if (size == 0 || collisionInfo.collisionSlotCnt == 0) {
2578        return 100;
2579      }
2580      int _metric0 = getHq0();
2581      int _metric1 = getHq1();
2582      int _metric2 = getHq2();
2583      if (_metric1 < _metric0) {
2584        int v = _metric0;
2585        _metric0 = _metric1;
2586        _metric1 = v;
2587      }
2588      if (_metric2 < _metric0) {
2589        int v = _metric0;
2590        _metric0 = _metric2;
2591        _metric2 = v;
2592      }
2593      if (_metric2 < _metric1) {
2594        int v = _metric1;
2595        _metric1 = _metric2;
2596        _metric2 = v;
2597      }
2598      if (_metric0 <= 0) {
2599        return 0;
2600      }
2601      _metric0 = _metric0 + ((_metric1 - 50) * 5 / _metric0);
2602      _metric0 = _metric0 + ((_metric2 - 50) * 2 / _metric0);
2603      _metric0 = Math.max(0, _metric0);
2604      _metric0 = Math.min(100, _metric0);
2605      return _metric0;
2606    }
2607    public double getMillisPerFetch() { return fetchCnt == 0 ? -1 : (fetchMillis * 1D / fetchCnt); }
2608    public long getFetchMillis() { return fetchMillis; }
2609    public int getCollisionCnt() { return collisionInfo.collisionCnt; }
2610    public int getCollisionSlotCnt() { return collisionInfo.collisionSlotCnt; }
2611    public int getLongestCollisionSize() { return collisionInfo.longestCollisionSize; }
2612    public String getIntegrityDescriptor() { return integrityState.getStateDescriptor(); }
2613    public long getStarted() { return startedTime; }
2614    public long getCleared() { return clearedTime; }
2615    public long getTouched() { return touchedTime; }
2616    public long getInfoCreated() { return creationTime; }
2617    public int getInfoCreationDeltaMs() { return creationDeltaMs; }
2618    public int getHealth() {
2619      if (storage != null && storage.getAlert() == 2) {
2620        return 2;
2621      }
2622      if (integrityState.getStateFlags() > 0 ||
2623          getHashQualityInteger() < 5) {
2624        return 2;
2625      }
2626      if (storage != null && storage.getAlert() == 1) {
2627        return 1;
2628      }
2629      if (getHashQualityInteger() < 30 ||
2630        getKeyMutationCnt() > 0 ||
2631        getInternalExceptionCnt() > 0) {
2632        return 1;
2633      }
2634      return 0;
2635    }
2636    public String getExtraStatistics() {
2637      return extraStatistics;
2638    }
2639
2640  }
2641
2642  static class CollisionInfo {
2643    int collisionCnt; int collisionSlotCnt; int longestCollisionSize;
2644  }
2645
2646  /**
2647   * This function calculates a modified hash code. The intention is to
2648   * "rehash" the incoming integer hash codes to overcome weak hash code
2649   * implementations. We expect good results for integers also.
2650   * Also add a random seed to the hash to protect against attacks on hashes.
2651   * This is actually a slightly reduced version of the java.util.HashMap
2652   * hash modification.
2653   */
2654  protected final int modifiedHash(int h) {
2655    h ^= hashSeed;
2656    h ^= h >>> 7;
2657    h ^= h >>> 15;
2658    return h;
2659
2660  }
2661
2662  protected class MyTimerTask extends java.util.TimerTask {
2663    E entry;
2664
2665    public void run() {
2666      timerEvent(entry, scheduledExecutionTime());
2667    }
2668  }
2669
2670  public static class Tunable extends TunableConstants {
2671
2672    /**
2673     * Implementation class to use by default.
2674     */
2675    public Class<? extends BaseCache> defaultImplementation = LruCache.class;
2676
2677    /**
2678     * Log exceptions from the source just as they happen. The log goes to the debug output
2679     * of the cache log, debug level of the cache log must be enabled also.
2680     */
2681    public boolean logSourceExceptions = false;
2682
2683    public int waitForTimerJobsSeconds = 5;
2684
2685    /**
2686     * Limits the number of spins until an entry lock is expected to
2687     * succeed. The limit is to detect deadlock issues during development
2688     * and testing. It is set to an arbitrary high value to result in
2689     * an exception after about one second of spinning.
2690     */
2691    public int maximumEntryLockSpins = 333333;
2692
2693    /**
2694     * Maximum number of tries to find an entry for eviction if maximum size
2695     * is reached.
2696     */
2697    public int maximumEvictSpins = 5;
2698
2699    /**
2700     * Size of the hash table before inserting the first entry. Must be power
2701     * of two. Default: 64.
2702     */
2703    public int initialHashSize = 64;
2704
2705    /**
2706     * Fill percentage limit. When this is reached the hash table will get
2707     * expanded. Default: 64.
2708     */
2709    public int hashLoadPercent = 64;
2710
2711    /**
2712     * The hash code will randomized by default. This is a countermeasure
2713     * against from outside that know the hash function.
2714     */
2715    public boolean disableHashRandomization = false;
2716
2717    /**
2718     * Seed used when randomization is disabled. Default: 0.
2719     */
2720    public int hashSeed = 0;
2721
2722    /**
2723     * When sharp expiry is enabled, the expiry timer goes
2724     * before the actual expiry to switch back to a time checking
2725     * scheme when the get method is invoked. This prevents
2726     * that an expired value gets served by the cache if the time
2727     * is too late. A recent GC should not produce more then 200
2728     * milliseconds stall. If longer GC stalls are expected, this
2729     * value needs to be changed. A value of LONG.MaxValue
2730     * suppresses the timer usage completely.
2731     */
2732    public long sharpExpirySafetyGapMillis = 666;
2733
2734
2735  }
2736
2737}