WindowCache.java

  1. /*
  2.  * Copyright (C) 2008, 2009 Google Inc.
  3.  * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.internal.storage.file;

  12. import java.io.IOException;
  13. import java.lang.ref.ReferenceQueue;
  14. import java.lang.ref.SoftReference;
  15. import java.util.Collections;
  16. import java.util.Map;
  17. import java.util.Random;
  18. import java.util.concurrent.ConcurrentHashMap;
  19. import java.util.concurrent.ConcurrentLinkedQueue;
  20. import java.util.concurrent.atomic.AtomicBoolean;
  21. import java.util.concurrent.atomic.AtomicLong;
  22. import java.util.concurrent.atomic.AtomicReferenceArray;
  23. import java.util.concurrent.atomic.LongAdder;
  24. import java.util.concurrent.locks.ReentrantLock;
  25. import java.util.stream.Collectors;

  26. import org.eclipse.jgit.annotations.NonNull;
  27. import org.eclipse.jgit.internal.JGitText;
  28. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  29. import org.eclipse.jgit.storage.file.WindowCacheStats;
  30. import org.eclipse.jgit.util.Monitoring;

  31. /**
  32.  * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.Pack} in
  33.  * memory for faster read access.
  34.  * <p>
  35.  * The WindowCache serves as a Java based "buffer cache", loading segments of a
  36.  * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
  37.  * only tiny slices of a file, the WindowCache tries to smooth out these tiny
  38.  * reads into larger block-sized IO operations.
  39.  * <p>
  40.  * Whenever a cache miss occurs, {@link #load(Pack, long)} is invoked by
  41.  * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
  42.  * This is ensured by an array of locks, with the tuple hashed to a lock
  43.  * instance.
  44.  * <p>
  45.  * During a miss, older entries are evicted from the cache so long as
  46.  * {@link #isFull()} returns true.
  47.  * <p>
  48.  * Its too expensive during object access to be 100% accurate with a least
  49.  * recently used (LRU) algorithm. Strictly ordering every read is a lot of
  50.  * overhead that typically doesn't yield a corresponding benefit to the
  51.  * application.
  52.  * <p>
  53.  * This cache implements a loose LRU policy by randomly picking a window
  54.  * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
  55.  * within that window.
  56.  * <p>
  57.  * Entities created by the cache are held under SoftReferences if option
  58.  * {@code core.packedGitUseStrongRefs} is set to {@code false} in the git config
  59.  * (this is the default) or by calling
  60.  * {@link WindowCacheConfig#setPackedGitUseStrongRefs(boolean)}, permitting the
  61.  * Java runtime's garbage collector to evict entries when heap memory gets low.
  62.  * Most JREs implement a loose least recently used algorithm for this eviction.
  63.  * When this option is set to {@code true} strong references are used which
  64.  * means that Java gc cannot evict the WindowCache to reclaim memory. On the
  65.  * other hand this provides more predictable performance since the cache isn't
  66.  * flushed when used heap comes close to the maximum heap size.
  67.  * <p>
  68.  * The internal hash table does not expand at runtime, instead it is fixed in
  69.  * size at cache creation time. The internal lock table used to gate load
  70.  * invocations is also fixed in size.
  71.  * <p>
  72.  * The key tuple is passed through to methods as a pair of parameters rather
  73.  * than as a single Object, thus reducing the transient memory allocations of
  74.  * callers. It is more efficient to avoid the allocation, as we can't be 100%
  75.  * sure that a JIT would be able to stack-allocate a key tuple.
  76.  * <p>
  77.  * This cache has an implementation rule such that:
  78.  * <ul>
  79.  * <li>{@link #load(Pack, long)} is invoked by at most one thread at a time
  80.  * for a given <code>(PackFile,position)</code> tuple.</li>
  81.  * <li>For every <code>load()</code> invocation there is exactly one
  82.  * {@link #createRef(Pack, long, ByteWindow)} invocation to wrap a
  83.  * SoftReference or a StrongReference around the cached entity.</li>
  84.  * <li>For every Reference created by <code>createRef()</code> there will be
  85.  * exactly one call to {@link #clear(PageRef)} to cleanup any resources associated
  86.  * with the (now expired) cached entity.</li>
  87.  * </ul>
  88.  * <p>
  89.  * Therefore, it is safe to perform resource accounting increments during the
  90.  * {@link #load(Pack, long)} or
  91.  * {@link #createRef(Pack, long, ByteWindow)} methods, and matching
  92.  * decrements during {@link #clear(PageRef)}. Implementors may need to override
  93.  * {@link #createRef(Pack, long, ByteWindow)} in order to embed additional
  94.  * accounting information into an implementation specific
  95.  * {@link org.eclipse.jgit.internal.storage.file.WindowCache.PageRef} subclass, as
  96.  * the cached entity may have already been evicted by the JRE's garbage
  97.  * collector.
  98.  * <p>
  99.  * To maintain higher concurrency workloads, during eviction only one thread
  100.  * performs the eviction work, while other threads can continue to insert new
  101.  * objects in parallel. This means that the cache can be temporarily over limit,
  102.  * especially if the nominated eviction thread is being starved relative to the
  103.  * other threads.
  104.  */
  105. public class WindowCache {

  106.     /**
  107.      * Record statistics for a cache
  108.      */
  109.     static interface StatsRecorder {
  110.         /**
  111.          * Record cache hits. Called when cache returns a cached entry.
  112.          *
  113.          * @param count
  114.          *            number of cache hits to record
  115.          */
  116.         void recordHits(int count);

  117.         /**
  118.          * Record cache misses. Called when the cache returns an entry which had
  119.          * to be loaded.
  120.          *
  121.          * @param count
  122.          *            number of cache misses to record
  123.          */
  124.         void recordMisses(int count);

  125.         /**
  126.          * Record a successful load of a cache entry
  127.          *
  128.          * @param loadTimeNanos
  129.          *            time to load a cache entry
  130.          */
  131.         void recordLoadSuccess(long loadTimeNanos);

  132.         /**
  133.          * Record a failed load of a cache entry
  134.          *
  135.          * @param loadTimeNanos
  136.          *            time used trying to load a cache entry
  137.          */
  138.         void recordLoadFailure(long loadTimeNanos);

  139.         /**
  140.          * Record cache evictions due to the cache evictions strategy
  141.          *
  142.          * @param count
  143.          *            number of evictions to record
  144.          */
  145.         void recordEvictions(int count);

  146.         /**
  147.          * Record files opened by cache
  148.          *
  149.          * @param delta
  150.          *            delta of number of files opened by cache
  151.          */
  152.         void recordOpenFiles(int delta);

  153.         /**
  154.          * Record cached bytes
  155.          *
  156.          * @param pack
  157.          *            pack file the bytes are read from
  158.          *
  159.          * @param delta
  160.          *            delta of cached bytes
  161.          */
  162.         void recordOpenBytes(Pack pack, int delta);

  163.         /**
  164.          * Returns a snapshot of this recorder's stats. Note that this may be an
  165.          * inconsistent view, as it may be interleaved with update operations.
  166.          *
  167.          * @return a snapshot of this recorder's stats
  168.          */
  169.         @NonNull
  170.         WindowCacheStats getStats();
  171.     }

  172.     static class StatsRecorderImpl
  173.             implements StatsRecorder, WindowCacheStats {
  174.         private final LongAdder hitCount;
  175.         private final LongAdder missCount;
  176.         private final LongAdder loadSuccessCount;
  177.         private final LongAdder loadFailureCount;
  178.         private final LongAdder totalLoadTime;
  179.         private final LongAdder evictionCount;
  180.         private final LongAdder openFileCount;
  181.         private final LongAdder openByteCount;
  182.         private final Map<String, LongAdder> openByteCountPerRepository;

  183.         /**
  184.          * Constructs an instance with all counts initialized to zero.
  185.          */
  186.         public StatsRecorderImpl() {
  187.             hitCount = new LongAdder();
  188.             missCount = new LongAdder();
  189.             loadSuccessCount = new LongAdder();
  190.             loadFailureCount = new LongAdder();
  191.             totalLoadTime = new LongAdder();
  192.             evictionCount = new LongAdder();
  193.             openFileCount = new LongAdder();
  194.             openByteCount = new LongAdder();
  195.             openByteCountPerRepository = new ConcurrentHashMap<>();
  196.         }

  197.         @Override
  198.         public void recordHits(int count) {
  199.             hitCount.add(count);
  200.         }

  201.         @Override
  202.         public void recordMisses(int count) {
  203.             missCount.add(count);
  204.         }

  205.         @Override
  206.         public void recordLoadSuccess(long loadTimeNanos) {
  207.             loadSuccessCount.increment();
  208.             totalLoadTime.add(loadTimeNanos);
  209.         }

  210.         @Override
  211.         public void recordLoadFailure(long loadTimeNanos) {
  212.             loadFailureCount.increment();
  213.             totalLoadTime.add(loadTimeNanos);
  214.         }

  215.         @Override
  216.         public void recordEvictions(int count) {
  217.             evictionCount.add(count);
  218.         }

  219.         @Override
  220.         public void recordOpenFiles(int delta) {
  221.             openFileCount.add(delta);
  222.         }

  223.         @Override
  224.         public void recordOpenBytes(Pack pack, int delta) {
  225.             openByteCount.add(delta);
  226.             String repositoryId = repositoryId(pack);
  227.             LongAdder la = openByteCountPerRepository
  228.                     .computeIfAbsent(repositoryId, k -> new LongAdder());
  229.             la.add(delta);
  230.             if (delta < 0) {
  231.                 openByteCountPerRepository.computeIfPresent(repositoryId,
  232.                         (k, v) -> v.longValue() == 0 ? null : v);
  233.             }
  234.         }

  235.         private static String repositoryId(Pack pack) {
  236.             // use repository's gitdir since Pack doesn't know its repository
  237.             return pack.getPackFile().getParentFile().getParentFile()
  238.                     .getParent();
  239.         }

  240.         @Override
  241.         public WindowCacheStats getStats() {
  242.             return this;
  243.         }

  244.         @Override
  245.         public long getHitCount() {
  246.             return hitCount.sum();
  247.         }

  248.         @Override
  249.         public long getMissCount() {
  250.             return missCount.sum();
  251.         }

  252.         @Override
  253.         public long getLoadSuccessCount() {
  254.             return loadSuccessCount.sum();
  255.         }

  256.         @Override
  257.         public long getLoadFailureCount() {
  258.             return loadFailureCount.sum();
  259.         }

  260.         @Override
  261.         public long getEvictionCount() {
  262.             return evictionCount.sum();
  263.         }

  264.         @Override
  265.         public long getTotalLoadTime() {
  266.             return totalLoadTime.sum();
  267.         }

  268.         @Override
  269.         public long getOpenFileCount() {
  270.             return openFileCount.sum();
  271.         }

  272.         @Override
  273.         public long getOpenByteCount() {
  274.             return openByteCount.sum();
  275.         }

  276.         @Override
  277.         public void resetCounters() {
  278.             hitCount.reset();
  279.             missCount.reset();
  280.             loadSuccessCount.reset();
  281.             loadFailureCount.reset();
  282.             totalLoadTime.reset();
  283.             evictionCount.reset();
  284.         }

  285.         @Override
  286.         public Map<String, Long> getOpenByteCountPerRepository() {
  287.             return Collections.unmodifiableMap(
  288.                     openByteCountPerRepository.entrySet().stream()
  289.                             .collect(Collectors.toMap(Map.Entry::getKey,
  290.                                     e -> Long.valueOf(e.getValue().sum()),
  291.                                     (u, v) -> v)));
  292.         }
  293.     }

  294.     private static final int bits(int newSize) {
  295.         if (newSize < 4096)
  296.             throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  297.         if (Integer.bitCount(newSize) != 1)
  298.             throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
  299.         return Integer.numberOfTrailingZeros(newSize);
  300.     }

  301.     private static final Random rng = new Random();

  302.     private static volatile WindowCache cache;

  303.     private static volatile int streamFileThreshold;

  304.     static {
  305.         reconfigure(new WindowCacheConfig());
  306.     }

  307.     /**
  308.      * Modify the configuration of the window cache.
  309.      * <p>
  310.      * The new configuration is applied immediately. If the new limits are
  311.      * smaller than what is currently cached, older entries will be purged
  312.      * as soon as possible to allow the cache to meet the new limit.
  313.      *
  314.      * @deprecated use {@code cfg.install()} to avoid internal reference.
  315.      * @param cfg
  316.      *            the new window cache configuration.
  317.      * @throws java.lang.IllegalArgumentException
  318.      *             the cache configuration contains one or more invalid
  319.      *             settings, usually too low of a limit.
  320.      */
  321.     @Deprecated
  322.     public static void reconfigure(WindowCacheConfig cfg) {
  323.         final WindowCache nc = new WindowCache(cfg);
  324.         final WindowCache oc = cache;
  325.         if (oc != null)
  326.             oc.removeAll();
  327.         cache = nc;
  328.         streamFileThreshold = cfg.getStreamFileThreshold();
  329.         DeltaBaseCache.reconfigure(cfg);
  330.     }

  331.     static int getStreamFileThreshold() {
  332.         return streamFileThreshold;
  333.     }

  334.     /**
  335.      * @return the cached instance.
  336.      */
  337.     public static WindowCache getInstance() {
  338.         return cache.publishMBeanIfNeeded();
  339.     }

  340.     static final ByteWindow get(Pack pack, long offset)
  341.             throws IOException {
  342.         final WindowCache c = cache;
  343.         final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
  344.         if (c != cache.publishMBeanIfNeeded()) {
  345.             // The cache was reconfigured while we were using the old one
  346.             // to load this window. The window is still valid, but our
  347.             // cache may think its still live. Ensure the window is removed
  348.             // from the old cache so resources can be released.
  349.             //
  350.             c.removeAll();
  351.         }
  352.         return r;
  353.     }

  354.     static final void purge(Pack pack) {
  355.         cache.removeAll(pack);
  356.     }

  357.     /** cleanup released and/or garbage collected windows. */
  358.     private final CleanupQueue queue;

  359.     /** Number of entries in {@link #table}. */
  360.     private final int tableSize;

  361.     /** Access clock for loose LRU. */
  362.     private final AtomicLong clock;

  363.     /** Hash bucket directory; entries are chained below. */
  364.     private final AtomicReferenceArray<Entry> table;

  365.     /** Locks to prevent concurrent loads for same (PackFile,position). */
  366.     private final Lock[] locks;

  367.     /** Lock to elect the eviction thread after a load occurs. */
  368.     private final ReentrantLock evictLock;

  369.     /** Number of {@link #table} buckets to scan for an eviction window. */
  370.     private final int evictBatch;

  371.     private final int maxFiles;

  372.     private final long maxBytes;

  373.     private final boolean mmap;

  374.     private final int windowSizeShift;

  375.     private final int windowSize;

  376.     private final StatsRecorder statsRecorder;

  377.     private final StatsRecorderImpl mbean;

  378.     private final AtomicBoolean publishMBean = new AtomicBoolean();

  379.     private boolean useStrongRefs;

  380.     private WindowCache(WindowCacheConfig cfg) {
  381.         tableSize = tableSize(cfg);
  382.         final int lockCount = lockCount(cfg);
  383.         if (tableSize < 1)
  384.             throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
  385.         if (lockCount < 1)
  386.             throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);

  387.         clock = new AtomicLong(1);
  388.         table = new AtomicReferenceArray<>(tableSize);
  389.         locks = new Lock[lockCount];
  390.         for (int i = 0; i < locks.length; i++)
  391.             locks[i] = new Lock();
  392.         evictLock = new ReentrantLock();

  393.         int eb = (int) (tableSize * .1);
  394.         if (64 < eb)
  395.             eb = 64;
  396.         else if (eb < 4)
  397.             eb = 4;
  398.         if (tableSize < eb)
  399.             eb = tableSize;
  400.         evictBatch = eb;

  401.         maxFiles = cfg.getPackedGitOpenFiles();
  402.         maxBytes = cfg.getPackedGitLimit();
  403.         mmap = cfg.isPackedGitMMAP();
  404.         windowSizeShift = bits(cfg.getPackedGitWindowSize());
  405.         windowSize = 1 << windowSizeShift;
  406.         useStrongRefs = cfg.isPackedGitUseStrongRefs();
  407.         queue = useStrongRefs ? new StrongCleanupQueue(this)
  408.                 : new SoftCleanupQueue(this);

  409.         mbean = new StatsRecorderImpl();
  410.         statsRecorder = mbean;
  411.         publishMBean.set(cfg.getExposeStatsViaJmx());

  412.         if (maxFiles < 1)
  413.             throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
  414.         if (maxBytes < windowSize)
  415.             throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  416.     }

  417.     private WindowCache publishMBeanIfNeeded() {
  418.         if (publishMBean.getAndSet(false)) {
  419.             Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
  420.         }
  421.         return this;
  422.     }

  423.     /**
  424.      * @return cache statistics for the WindowCache
  425.      */
  426.     public WindowCacheStats getStats() {
  427.         return statsRecorder.getStats();
  428.     }

  429.     /**
  430.      * Reset stats. Does not reset open bytes and open files stats.
  431.      */
  432.     public void resetStats() {
  433.         mbean.resetCounters();
  434.     }

  435.     private int hash(int packHash, long off) {
  436.         return packHash + (int) (off >>> windowSizeShift);
  437.     }

  438.     private ByteWindow load(Pack pack, long offset) throws IOException {
  439.         long startTime = System.nanoTime();
  440.         if (pack.beginWindowCache())
  441.             statsRecorder.recordOpenFiles(1);
  442.         try {
  443.             if (mmap)
  444.                 return pack.mmap(offset, windowSize);
  445.             ByteArrayWindow w = pack.read(offset, windowSize);
  446.             statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
  447.             return w;
  448.         } catch (IOException | RuntimeException | Error e) {
  449.             close(pack);
  450.             statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
  451.             throw e;
  452.         } finally {
  453.             statsRecorder.recordMisses(1);
  454.         }
  455.     }

  456.     private PageRef<ByteWindow> createRef(Pack p, long o, ByteWindow v) {
  457.         final PageRef<ByteWindow> ref = useStrongRefs
  458.                 ? new StrongRef(p, o, v, queue)
  459.                 : new SoftRef(p, o, v, (SoftCleanupQueue) queue);
  460.         statsRecorder.recordOpenBytes(ref.getPack(), ref.getSize());
  461.         return ref;
  462.     }

  463.     private void clear(PageRef<ByteWindow> ref) {
  464.         statsRecorder.recordOpenBytes(ref.getPack(), -ref.getSize());
  465.         statsRecorder.recordEvictions(1);
  466.         close(ref.getPack());
  467.     }

  468.     private void close(Pack pack) {
  469.         if (pack.endWindowCache()) {
  470.             statsRecorder.recordOpenFiles(-1);
  471.         }
  472.     }

  473.     private boolean isFull() {
  474.         return maxFiles < mbean.getOpenFileCount()
  475.                 || maxBytes < mbean.getOpenByteCount();
  476.     }

  477.     private long toStart(long offset) {
  478.         return (offset >>> windowSizeShift) << windowSizeShift;
  479.     }

  480.     private static int tableSize(WindowCacheConfig cfg) {
  481.         final int wsz = cfg.getPackedGitWindowSize();
  482.         final long limit = cfg.getPackedGitLimit();
  483.         if (wsz <= 0)
  484.             throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  485.         if (limit < wsz)
  486.             throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  487.         return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
  488.     }

  489.     private static int lockCount(WindowCacheConfig cfg) {
  490.         return Math.max(cfg.getPackedGitOpenFiles(), 32);
  491.     }

  492.     /**
  493.      * Lookup a cached object, creating and loading it if it doesn't exist.
  494.      *
  495.      * @param pack
  496.      *            the pack that "contains" the cached object.
  497.      * @param position
  498.      *            offset within <code>pack</code> of the object.
  499.      * @return the object reference.
  500.      * @throws IOException
  501.      *             the object reference was not in the cache and could not be
  502.      *             obtained by {@link #load(Pack, long)}.
  503.      */
  504.     private ByteWindow getOrLoad(Pack pack, long position)
  505.             throws IOException {
  506.         final int slot = slot(pack, position);
  507.         final Entry e1 = table.get(slot);
  508.         ByteWindow v = scan(e1, pack, position);
  509.         if (v != null) {
  510.             statsRecorder.recordHits(1);
  511.             return v;
  512.         }

  513.         synchronized (lock(pack, position)) {
  514.             Entry e2 = table.get(slot);
  515.             if (e2 != e1) {
  516.                 v = scan(e2, pack, position);
  517.                 if (v != null) {
  518.                     statsRecorder.recordHits(1);
  519.                     return v;
  520.                 }
  521.             }

  522.             v = load(pack, position);
  523.             final PageRef<ByteWindow> ref = createRef(pack, position, v);
  524.             hit(ref);
  525.             for (;;) {
  526.                 final Entry n = new Entry(clean(e2), ref);
  527.                 if (table.compareAndSet(slot, e2, n))
  528.                     break;
  529.                 e2 = table.get(slot);
  530.             }
  531.         }

  532.         if (evictLock.tryLock()) {
  533.             try {
  534.                 gc();
  535.                 evict();
  536.             } finally {
  537.                 evictLock.unlock();
  538.             }
  539.         }

  540.         return v;
  541.     }

  542.     private ByteWindow scan(Entry n, Pack pack, long position) {
  543.         for (; n != null; n = n.next) {
  544.             final PageRef<ByteWindow> r = n.ref;
  545.             if (r.getPack() == pack && r.getPosition() == position) {
  546.                 final ByteWindow v = r.get();
  547.                 if (v != null) {
  548.                     hit(r);
  549.                     return v;
  550.                 }
  551.                 n.kill();
  552.                 break;
  553.             }
  554.         }
  555.         return null;
  556.     }

  557.     private void hit(PageRef r) {
  558.         // We don't need to be 100% accurate here. Its sufficient that at least
  559.         // one thread performs the increment. Any other concurrent access at
  560.         // exactly the same time can simply use the same clock value.
  561.         //
  562.         // Consequently we attempt the set, but we don't try to recover should
  563.         // it fail. This is why we don't use getAndIncrement() here.
  564.         //
  565.         final long c = clock.get();
  566.         clock.compareAndSet(c, c + 1);
  567.         r.setLastAccess(c);
  568.     }

  569.     private void evict() {
  570.         while (isFull()) {
  571.             int ptr = rng.nextInt(tableSize);
  572.             Entry old = null;
  573.             int slot = 0;
  574.             for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
  575.                 if (tableSize <= ptr)
  576.                     ptr = 0;
  577.                 for (Entry e = table.get(ptr); e != null; e = e.next) {
  578.                     if (e.dead)
  579.                         continue;
  580.                     if (old == null || e.ref.getLastAccess() < old.ref
  581.                             .getLastAccess()) {
  582.                         old = e;
  583.                         slot = ptr;
  584.                     }
  585.                 }
  586.             }
  587.             if (old != null) {
  588.                 old.kill();
  589.                 gc();
  590.                 final Entry e1 = table.get(slot);
  591.                 table.compareAndSet(slot, e1, clean(e1));
  592.             }
  593.         }
  594.     }

  595.     /**
  596.      * Clear every entry from the cache.
  597.      * <p>
  598.      * This is a last-ditch effort to clear out the cache, such as before it
  599.      * gets replaced by another cache that is configured differently. This
  600.      * method tries to force every cached entry through {@link #clear(PageRef)} to
  601.      * ensure that resources are correctly accounted for and cleaned up by the
  602.      * subclass. A concurrent reader loading entries while this method is
  603.      * running may cause resource accounting failures.
  604.      */
  605.     private void removeAll() {
  606.         for (int s = 0; s < tableSize; s++) {
  607.             Entry e1;
  608.             do {
  609.                 e1 = table.get(s);
  610.                 for (Entry e = e1; e != null; e = e.next)
  611.                     e.kill();
  612.             } while (!table.compareAndSet(s, e1, null));
  613.         }
  614.         gc();
  615.     }

  616.     /**
  617.      * Clear all entries related to a single file.
  618.      * <p>
  619.      * Typically this method is invoked during {@link Pack#close()}, when we
  620.      * know the pack is never going to be useful to us again (for example, it no
  621.      * longer exists on disk). A concurrent reader loading an entry from this
  622.      * same pack may cause the pack to become stuck in the cache anyway.
  623.      *
  624.      * @param pack
  625.      *            the file to purge all entries of.
  626.      */
  627.     private void removeAll(Pack pack) {
  628.         for (int s = 0; s < tableSize; s++) {
  629.             final Entry e1 = table.get(s);
  630.             boolean hasDead = false;
  631.             for (Entry e = e1; e != null; e = e.next) {
  632.                 if (e.ref.getPack() == pack) {
  633.                     e.kill();
  634.                     hasDead = true;
  635.                 } else if (e.dead)
  636.                     hasDead = true;
  637.             }
  638.             if (hasDead)
  639.                 table.compareAndSet(s, e1, clean(e1));
  640.         }
  641.         gc();
  642.     }

  643.     private void gc() {
  644.         queue.gc();
  645.     }

  646.     private int slot(Pack pack, long position) {
  647.         return (hash(pack.hash, position) >>> 1) % tableSize;
  648.     }

  649.     private Lock lock(Pack pack, long position) {
  650.         return locks[(hash(pack.hash, position) >>> 1) % locks.length];
  651.     }

  652.     private static Entry clean(Entry top) {
  653.         while (top != null && top.dead) {
  654.             top.ref.kill();
  655.             top = top.next;
  656.         }
  657.         if (top == null)
  658.             return null;
  659.         final Entry n = clean(top.next);
  660.         return n == top.next ? top : new Entry(n, top.ref);
  661.     }

  662.     private static class Entry {
  663.         /** Next entry in the hash table's chain list. */
  664.         final Entry next;

  665.         /** The referenced object. */
  666.         final PageRef<ByteWindow> ref;

  667.         /**
  668.          * Marked true when ref.get() returns null and the ref is dead.
  669.          * <p>
  670.          * A true here indicates that the ref is no longer accessible, and that
  671.          * we therefore need to eventually purge this Entry object out of the
  672.          * bucket's chain.
  673.          */
  674.         volatile boolean dead;

  675.         Entry(Entry n, PageRef<ByteWindow> r) {
  676.             next = n;
  677.             ref = r;
  678.         }

  679.         final void kill() {
  680.             dead = true;
  681.             ref.kill();
  682.         }
  683.     }

  684.     private static interface PageRef<T> {
  685.         /**
  686.          * Returns this reference object's referent. If this reference object
  687.          * has been cleared, either by the program or by the garbage collector,
  688.          * then this method returns <code>null</code>.
  689.          *
  690.          * @return The object to which this reference refers, or
  691.          *         <code>null</code> if this reference object has been cleared
  692.          */
  693.         T get();

  694.         /**
  695.          * Kill this ref
  696.          *
  697.          * @return <code>true</code> if this reference object was successfully
  698.          *         killed; <code>false</code> if it was already killed
  699.          */
  700.         boolean kill();

  701.         /**
  702.          * Get the {@link org.eclipse.jgit.internal.storage.file.Pack} the
  703.          * referenced cache page is allocated for
  704.          *
  705.          * @return the {@link org.eclipse.jgit.internal.storage.file.Pack} the
  706.          *         referenced cache page is allocated for
  707.          */
  708.         Pack getPack();

  709.         /**
  710.          * Get the position of the referenced cache page in the
  711.          * {@link org.eclipse.jgit.internal.storage.file.Pack}
  712.          *
  713.          * @return the position of the referenced cache page in the
  714.          *         {@link org.eclipse.jgit.internal.storage.file.Pack}
  715.          */
  716.         long getPosition();

  717.         /**
  718.          * Get size of cache page
  719.          *
  720.          * @return size of cache page
  721.          */
  722.         int getSize();

  723.         /**
  724.          * Get pseudo time of last access to this cache page
  725.          *
  726.          * @return pseudo time of last access to this cache page
  727.          */
  728.         long getLastAccess();

  729.         /**
  730.          * Set pseudo time of last access to this cache page
  731.          *
  732.          * @param time
  733.          *            pseudo time of last access to this cache page
  734.          */
  735.         void setLastAccess(long time);

  736.         /**
  737.          * Whether this is a strong reference.
  738.          * @return {@code true} if this is a strong reference
  739.          */
  740.         boolean isStrongRef();
  741.     }

  742.     /** A soft reference wrapped around a cached object. */
  743.     private static class SoftRef extends SoftReference<ByteWindow>
  744.             implements PageRef<ByteWindow> {
  745.         private final Pack pack;

  746.         private final long position;

  747.         private final int size;

  748.         private long lastAccess;

  749.         protected SoftRef(final Pack pack, final long position,
  750.                 final ByteWindow v, final SoftCleanupQueue queue) {
  751.             super(v, queue);
  752.             this.pack = pack;
  753.             this.position = position;
  754.             this.size = v.size();
  755.         }

  756.         @Override
  757.         public Pack getPack() {
  758.             return pack;
  759.         }

  760.         @Override
  761.         public long getPosition() {
  762.             return position;
  763.         }

  764.         @Override
  765.         public int getSize() {
  766.             return size;
  767.         }

  768.         @Override
  769.         public long getLastAccess() {
  770.             return lastAccess;
  771.         }

  772.         @Override
  773.         public void setLastAccess(long time) {
  774.             this.lastAccess = time;
  775.         }

  776.         @Override
  777.         public boolean kill() {
  778.             return enqueue();
  779.         }

  780.         @Override
  781.         public boolean isStrongRef() {
  782.             return false;
  783.         }
  784.     }

  785.     /** A strong reference wrapped around a cached object. */
  786.     private static class StrongRef implements PageRef<ByteWindow> {
  787.         private ByteWindow referent;

  788.         private final Pack pack;

  789.         private final long position;

  790.         private final int size;

  791.         private long lastAccess;

  792.         private CleanupQueue queue;

  793.         protected StrongRef(final Pack pack, final long position,
  794.                 final ByteWindow v, final CleanupQueue queue) {
  795.             this.pack = pack;
  796.             this.position = position;
  797.             this.referent = v;
  798.             this.size = v.size();
  799.             this.queue = queue;
  800.         }

  801.         @Override
  802.         public Pack getPack() {
  803.             return pack;
  804.         }

  805.         @Override
  806.         public long getPosition() {
  807.             return position;
  808.         }

  809.         @Override
  810.         public int getSize() {
  811.             return size;
  812.         }

  813.         @Override
  814.         public long getLastAccess() {
  815.             return lastAccess;
  816.         }

  817.         @Override
  818.         public void setLastAccess(long time) {
  819.             this.lastAccess = time;
  820.         }

  821.         @Override
  822.         public ByteWindow get() {
  823.             return referent;
  824.         }

  825.         @Override
  826.         public boolean kill() {
  827.             if (referent == null) {
  828.                 return false;
  829.             }
  830.             referent = null;
  831.             return queue.enqueue(this);
  832.         }

  833.         @Override
  834.         public boolean isStrongRef() {
  835.             return true;
  836.         }
  837.     }

  838.     private static interface CleanupQueue {
  839.         boolean enqueue(PageRef<ByteWindow> r);
  840.         void gc();
  841.     }

  842.     private static class SoftCleanupQueue extends ReferenceQueue<ByteWindow>
  843.             implements CleanupQueue {
  844.         private final WindowCache wc;

  845.         SoftCleanupQueue(WindowCache cache) {
  846.             this.wc = cache;
  847.         }

  848.         @Override
  849.         public boolean enqueue(PageRef<ByteWindow> r) {
  850.             // no need to explicitly add soft references which are enqueued by
  851.             // the JVM
  852.             return false;
  853.         }

  854.         @Override
  855.         public void gc() {
  856.             SoftRef r;
  857.             while ((r = (SoftRef) poll()) != null) {
  858.                 wc.clear(r);

  859.                 final int s = wc.slot(r.getPack(), r.getPosition());
  860.                 final Entry e1 = wc.table.get(s);
  861.                 for (Entry n = e1; n != null; n = n.next) {
  862.                     if (n.ref == r) {
  863.                         n.dead = true;
  864.                         wc.table.compareAndSet(s, e1, clean(e1));
  865.                         break;
  866.                     }
  867.                 }
  868.             }
  869.         }
  870.     }

  871.     private static class StrongCleanupQueue implements CleanupQueue {
  872.         private final WindowCache wc;

  873.         private final ConcurrentLinkedQueue<PageRef<ByteWindow>> queue = new ConcurrentLinkedQueue<>();

  874.         StrongCleanupQueue(WindowCache wc) {
  875.             this.wc = wc;
  876.         }

  877.         @Override
  878.         public boolean enqueue(PageRef<ByteWindow> r) {
  879.             if (queue.contains(r)) {
  880.                 return false;
  881.             }
  882.             return queue.add(r);
  883.         }

  884.         @Override
  885.         public void gc() {
  886.             PageRef<ByteWindow> r;
  887.             while ((r = queue.poll()) != null) {
  888.                 wc.clear(r);

  889.                 final int s = wc.slot(r.getPack(), r.getPosition());
  890.                 final Entry e1 = wc.table.get(s);
  891.                 for (Entry n = e1; n != null; n = n.next) {
  892.                     if (n.ref == r) {
  893.                         n.dead = true;
  894.                         wc.table.compareAndSet(s, e1, clean(e1));
  895.                         break;
  896.                     }
  897.                 }
  898.             }
  899.         }
  900.     }

  901.     private static final class Lock {
  902.         // Used only for its implicit monitor.
  903.     }
  904. }