WindowCache.java
- /*
- * Copyright (C) 2008, 2009 Google Inc.
- * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.internal.storage.file;
- import java.io.IOException;
- import java.lang.ref.ReferenceQueue;
- import java.lang.ref.SoftReference;
- import java.util.Collections;
- import java.util.Map;
- import java.util.Random;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentLinkedQueue;
- import java.util.concurrent.atomic.AtomicBoolean;
- import java.util.concurrent.atomic.AtomicLong;
- import java.util.concurrent.atomic.AtomicReferenceArray;
- import java.util.concurrent.atomic.LongAdder;
- import java.util.concurrent.locks.ReentrantLock;
- import java.util.stream.Collectors;
- import org.eclipse.jgit.annotations.NonNull;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.storage.file.WindowCacheConfig;
- import org.eclipse.jgit.storage.file.WindowCacheStats;
- import org.eclipse.jgit.util.Monitoring;
- /**
- * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.Pack} in
- * memory for faster read access.
- * <p>
- * The WindowCache serves as a Java based "buffer cache", loading segments of a
- * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
- * only tiny slices of a file, the WindowCache tries to smooth out these tiny
- * reads into larger block-sized IO operations.
- * <p>
- * Whenever a cache miss occurs, {@link #load(Pack, long)} is invoked by
- * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
- * This is ensured by an array of locks, with the tuple hashed to a lock
- * instance.
- * <p>
- * During a miss, older entries are evicted from the cache so long as
- * {@link #isFull()} returns true.
- * <p>
- * Its too expensive during object access to be 100% accurate with a least
- * recently used (LRU) algorithm. Strictly ordering every read is a lot of
- * overhead that typically doesn't yield a corresponding benefit to the
- * application.
- * <p>
- * This cache implements a loose LRU policy by randomly picking a window
- * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
- * within that window.
- * <p>
- * Entities created by the cache are held under SoftReferences if option
- * {@code core.packedGitUseStrongRefs} is set to {@code false} in the git config
- * (this is the default) or by calling
- * {@link WindowCacheConfig#setPackedGitUseStrongRefs(boolean)}, permitting the
- * Java runtime's garbage collector to evict entries when heap memory gets low.
- * Most JREs implement a loose least recently used algorithm for this eviction.
- * When this option is set to {@code true} strong references are used which
- * means that Java gc cannot evict the WindowCache to reclaim memory. On the
- * other hand this provides more predictable performance since the cache isn't
- * flushed when used heap comes close to the maximum heap size.
- * <p>
- * The internal hash table does not expand at runtime, instead it is fixed in
- * size at cache creation time. The internal lock table used to gate load
- * invocations is also fixed in size.
- * <p>
- * The key tuple is passed through to methods as a pair of parameters rather
- * than as a single Object, thus reducing the transient memory allocations of
- * callers. It is more efficient to avoid the allocation, as we can't be 100%
- * sure that a JIT would be able to stack-allocate a key tuple.
- * <p>
- * This cache has an implementation rule such that:
- * <ul>
- * <li>{@link #load(Pack, long)} is invoked by at most one thread at a time
- * for a given <code>(PackFile,position)</code> tuple.</li>
- * <li>For every <code>load()</code> invocation there is exactly one
- * {@link #createRef(Pack, long, ByteWindow)} invocation to wrap a
- * SoftReference or a StrongReference around the cached entity.</li>
- * <li>For every Reference created by <code>createRef()</code> there will be
- * exactly one call to {@link #clear(PageRef)} to cleanup any resources associated
- * with the (now expired) cached entity.</li>
- * </ul>
- * <p>
- * Therefore, it is safe to perform resource accounting increments during the
- * {@link #load(Pack, long)} or
- * {@link #createRef(Pack, long, ByteWindow)} methods, and matching
- * decrements during {@link #clear(PageRef)}. Implementors may need to override
- * {@link #createRef(Pack, long, ByteWindow)} in order to embed additional
- * accounting information into an implementation specific
- * {@link org.eclipse.jgit.internal.storage.file.WindowCache.PageRef} subclass, as
- * the cached entity may have already been evicted by the JRE's garbage
- * collector.
- * <p>
- * To maintain higher concurrency workloads, during eviction only one thread
- * performs the eviction work, while other threads can continue to insert new
- * objects in parallel. This means that the cache can be temporarily over limit,
- * especially if the nominated eviction thread is being starved relative to the
- * other threads.
- */
- public class WindowCache {
- /**
- * Record statistics for a cache
- */
- static interface StatsRecorder {
- /**
- * Record cache hits. Called when cache returns a cached entry.
- *
- * @param count
- * number of cache hits to record
- */
- void recordHits(int count);
- /**
- * Record cache misses. Called when the cache returns an entry which had
- * to be loaded.
- *
- * @param count
- * number of cache misses to record
- */
- void recordMisses(int count);
- /**
- * Record a successful load of a cache entry
- *
- * @param loadTimeNanos
- * time to load a cache entry
- */
- void recordLoadSuccess(long loadTimeNanos);
- /**
- * Record a failed load of a cache entry
- *
- * @param loadTimeNanos
- * time used trying to load a cache entry
- */
- void recordLoadFailure(long loadTimeNanos);
- /**
- * Record cache evictions due to the cache evictions strategy
- *
- * @param count
- * number of evictions to record
- */
- void recordEvictions(int count);
- /**
- * Record files opened by cache
- *
- * @param delta
- * delta of number of files opened by cache
- */
- void recordOpenFiles(int delta);
- /**
- * Record cached bytes
- *
- * @param pack
- * pack file the bytes are read from
- *
- * @param delta
- * delta of cached bytes
- */
- void recordOpenBytes(Pack pack, int delta);
- /**
- * Returns a snapshot of this recorder's stats. Note that this may be an
- * inconsistent view, as it may be interleaved with update operations.
- *
- * @return a snapshot of this recorder's stats
- */
- @NonNull
- WindowCacheStats getStats();
- }
- static class StatsRecorderImpl
- implements StatsRecorder, WindowCacheStats {
- private final LongAdder hitCount;
- private final LongAdder missCount;
- private final LongAdder loadSuccessCount;
- private final LongAdder loadFailureCount;
- private final LongAdder totalLoadTime;
- private final LongAdder evictionCount;
- private final LongAdder openFileCount;
- private final LongAdder openByteCount;
- private final Map<String, LongAdder> openByteCountPerRepository;
- /**
- * Constructs an instance with all counts initialized to zero.
- */
- public StatsRecorderImpl() {
- hitCount = new LongAdder();
- missCount = new LongAdder();
- loadSuccessCount = new LongAdder();
- loadFailureCount = new LongAdder();
- totalLoadTime = new LongAdder();
- evictionCount = new LongAdder();
- openFileCount = new LongAdder();
- openByteCount = new LongAdder();
- openByteCountPerRepository = new ConcurrentHashMap<>();
- }
- @Override
- public void recordHits(int count) {
- hitCount.add(count);
- }
- @Override
- public void recordMisses(int count) {
- missCount.add(count);
- }
- @Override
- public void recordLoadSuccess(long loadTimeNanos) {
- loadSuccessCount.increment();
- totalLoadTime.add(loadTimeNanos);
- }
- @Override
- public void recordLoadFailure(long loadTimeNanos) {
- loadFailureCount.increment();
- totalLoadTime.add(loadTimeNanos);
- }
- @Override
- public void recordEvictions(int count) {
- evictionCount.add(count);
- }
- @Override
- public void recordOpenFiles(int delta) {
- openFileCount.add(delta);
- }
- @Override
- public void recordOpenBytes(Pack pack, int delta) {
- openByteCount.add(delta);
- String repositoryId = repositoryId(pack);
- LongAdder la = openByteCountPerRepository
- .computeIfAbsent(repositoryId, k -> new LongAdder());
- la.add(delta);
- if (delta < 0) {
- openByteCountPerRepository.computeIfPresent(repositoryId,
- (k, v) -> v.longValue() == 0 ? null : v);
- }
- }
- private static String repositoryId(Pack pack) {
- // use repository's gitdir since Pack doesn't know its repository
- return pack.getPackFile().getParentFile().getParentFile()
- .getParent();
- }
- @Override
- public WindowCacheStats getStats() {
- return this;
- }
- @Override
- public long getHitCount() {
- return hitCount.sum();
- }
- @Override
- public long getMissCount() {
- return missCount.sum();
- }
- @Override
- public long getLoadSuccessCount() {
- return loadSuccessCount.sum();
- }
- @Override
- public long getLoadFailureCount() {
- return loadFailureCount.sum();
- }
- @Override
- public long getEvictionCount() {
- return evictionCount.sum();
- }
- @Override
- public long getTotalLoadTime() {
- return totalLoadTime.sum();
- }
- @Override
- public long getOpenFileCount() {
- return openFileCount.sum();
- }
- @Override
- public long getOpenByteCount() {
- return openByteCount.sum();
- }
- @Override
- public void resetCounters() {
- hitCount.reset();
- missCount.reset();
- loadSuccessCount.reset();
- loadFailureCount.reset();
- totalLoadTime.reset();
- evictionCount.reset();
- }
- @Override
- public Map<String, Long> getOpenByteCountPerRepository() {
- return Collections.unmodifiableMap(
- openByteCountPerRepository.entrySet().stream()
- .collect(Collectors.toMap(Map.Entry::getKey,
- e -> Long.valueOf(e.getValue().sum()),
- (u, v) -> v)));
- }
- }
- private static final int bits(int newSize) {
- if (newSize < 4096)
- throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
- if (Integer.bitCount(newSize) != 1)
- throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
- return Integer.numberOfTrailingZeros(newSize);
- }
- private static final Random rng = new Random();
- private static volatile WindowCache cache;
- private static volatile int streamFileThreshold;
- static {
- reconfigure(new WindowCacheConfig());
- }
- /**
- * Modify the configuration of the window cache.
- * <p>
- * The new configuration is applied immediately. If the new limits are
- * smaller than what is currently cached, older entries will be purged
- * as soon as possible to allow the cache to meet the new limit.
- *
- * @deprecated use {@code cfg.install()} to avoid internal reference.
- * @param cfg
- * the new window cache configuration.
- * @throws java.lang.IllegalArgumentException
- * the cache configuration contains one or more invalid
- * settings, usually too low of a limit.
- */
- @Deprecated
- public static void reconfigure(WindowCacheConfig cfg) {
- final WindowCache nc = new WindowCache(cfg);
- final WindowCache oc = cache;
- if (oc != null)
- oc.removeAll();
- cache = nc;
- streamFileThreshold = cfg.getStreamFileThreshold();
- DeltaBaseCache.reconfigure(cfg);
- }
- static int getStreamFileThreshold() {
- return streamFileThreshold;
- }
- /**
- * @return the cached instance.
- */
- public static WindowCache getInstance() {
- return cache.publishMBeanIfNeeded();
- }
- static final ByteWindow get(Pack pack, long offset)
- throws IOException {
- final WindowCache c = cache;
- final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
- if (c != cache.publishMBeanIfNeeded()) {
- // The cache was reconfigured while we were using the old one
- // to load this window. The window is still valid, but our
- // cache may think its still live. Ensure the window is removed
- // from the old cache so resources can be released.
- //
- c.removeAll();
- }
- return r;
- }
- static final void purge(Pack pack) {
- cache.removeAll(pack);
- }
- /** cleanup released and/or garbage collected windows. */
- private final CleanupQueue queue;
- /** Number of entries in {@link #table}. */
- private final int tableSize;
- /** Access clock for loose LRU. */
- private final AtomicLong clock;
- /** Hash bucket directory; entries are chained below. */
- private final AtomicReferenceArray<Entry> table;
- /** Locks to prevent concurrent loads for same (PackFile,position). */
- private final Lock[] locks;
- /** Lock to elect the eviction thread after a load occurs. */
- private final ReentrantLock evictLock;
- /** Number of {@link #table} buckets to scan for an eviction window. */
- private final int evictBatch;
- private final int maxFiles;
- private final long maxBytes;
- private final boolean mmap;
- private final int windowSizeShift;
- private final int windowSize;
- private final StatsRecorder statsRecorder;
- private final StatsRecorderImpl mbean;
- private final AtomicBoolean publishMBean = new AtomicBoolean();
- private boolean useStrongRefs;
- private WindowCache(WindowCacheConfig cfg) {
- tableSize = tableSize(cfg);
- final int lockCount = lockCount(cfg);
- if (tableSize < 1)
- throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
- if (lockCount < 1)
- throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
- clock = new AtomicLong(1);
- table = new AtomicReferenceArray<>(tableSize);
- locks = new Lock[lockCount];
- for (int i = 0; i < locks.length; i++)
- locks[i] = new Lock();
- evictLock = new ReentrantLock();
- int eb = (int) (tableSize * .1);
- if (64 < eb)
- eb = 64;
- else if (eb < 4)
- eb = 4;
- if (tableSize < eb)
- eb = tableSize;
- evictBatch = eb;
- maxFiles = cfg.getPackedGitOpenFiles();
- maxBytes = cfg.getPackedGitLimit();
- mmap = cfg.isPackedGitMMAP();
- windowSizeShift = bits(cfg.getPackedGitWindowSize());
- windowSize = 1 << windowSizeShift;
- useStrongRefs = cfg.isPackedGitUseStrongRefs();
- queue = useStrongRefs ? new StrongCleanupQueue(this)
- : new SoftCleanupQueue(this);
- mbean = new StatsRecorderImpl();
- statsRecorder = mbean;
- publishMBean.set(cfg.getExposeStatsViaJmx());
- if (maxFiles < 1)
- throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
- if (maxBytes < windowSize)
- throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
- }
- private WindowCache publishMBeanIfNeeded() {
- if (publishMBean.getAndSet(false)) {
- Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
- }
- return this;
- }
- /**
- * @return cache statistics for the WindowCache
- */
- public WindowCacheStats getStats() {
- return statsRecorder.getStats();
- }
- /**
- * Reset stats. Does not reset open bytes and open files stats.
- */
- public void resetStats() {
- mbean.resetCounters();
- }
- private int hash(int packHash, long off) {
- return packHash + (int) (off >>> windowSizeShift);
- }
- private ByteWindow load(Pack pack, long offset) throws IOException {
- long startTime = System.nanoTime();
- if (pack.beginWindowCache())
- statsRecorder.recordOpenFiles(1);
- try {
- if (mmap)
- return pack.mmap(offset, windowSize);
- ByteArrayWindow w = pack.read(offset, windowSize);
- statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
- return w;
- } catch (IOException | RuntimeException | Error e) {
- close(pack);
- statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
- throw e;
- } finally {
- statsRecorder.recordMisses(1);
- }
- }
- private PageRef<ByteWindow> createRef(Pack p, long o, ByteWindow v) {
- final PageRef<ByteWindow> ref = useStrongRefs
- ? new StrongRef(p, o, v, queue)
- : new SoftRef(p, o, v, (SoftCleanupQueue) queue);
- statsRecorder.recordOpenBytes(ref.getPack(), ref.getSize());
- return ref;
- }
- private void clear(PageRef<ByteWindow> ref) {
- statsRecorder.recordOpenBytes(ref.getPack(), -ref.getSize());
- statsRecorder.recordEvictions(1);
- close(ref.getPack());
- }
- private void close(Pack pack) {
- if (pack.endWindowCache()) {
- statsRecorder.recordOpenFiles(-1);
- }
- }
- private boolean isFull() {
- return maxFiles < mbean.getOpenFileCount()
- || maxBytes < mbean.getOpenByteCount();
- }
- private long toStart(long offset) {
- return (offset >>> windowSizeShift) << windowSizeShift;
- }
- private static int tableSize(WindowCacheConfig cfg) {
- final int wsz = cfg.getPackedGitWindowSize();
- final long limit = cfg.getPackedGitLimit();
- if (wsz <= 0)
- throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
- if (limit < wsz)
- throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
- return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
- }
- private static int lockCount(WindowCacheConfig cfg) {
- return Math.max(cfg.getPackedGitOpenFiles(), 32);
- }
- /**
- * Lookup a cached object, creating and loading it if it doesn't exist.
- *
- * @param pack
- * the pack that "contains" the cached object.
- * @param position
- * offset within <code>pack</code> of the object.
- * @return the object reference.
- * @throws IOException
- * the object reference was not in the cache and could not be
- * obtained by {@link #load(Pack, long)}.
- */
- private ByteWindow getOrLoad(Pack pack, long position)
- throws IOException {
- final int slot = slot(pack, position);
- final Entry e1 = table.get(slot);
- ByteWindow v = scan(e1, pack, position);
- if (v != null) {
- statsRecorder.recordHits(1);
- return v;
- }
- synchronized (lock(pack, position)) {
- Entry e2 = table.get(slot);
- if (e2 != e1) {
- v = scan(e2, pack, position);
- if (v != null) {
- statsRecorder.recordHits(1);
- return v;
- }
- }
- v = load(pack, position);
- final PageRef<ByteWindow> ref = createRef(pack, position, v);
- hit(ref);
- for (;;) {
- final Entry n = new Entry(clean(e2), ref);
- if (table.compareAndSet(slot, e2, n))
- break;
- e2 = table.get(slot);
- }
- }
- if (evictLock.tryLock()) {
- try {
- gc();
- evict();
- } finally {
- evictLock.unlock();
- }
- }
- return v;
- }
- private ByteWindow scan(Entry n, Pack pack, long position) {
- for (; n != null; n = n.next) {
- final PageRef<ByteWindow> r = n.ref;
- if (r.getPack() == pack && r.getPosition() == position) {
- final ByteWindow v = r.get();
- if (v != null) {
- hit(r);
- return v;
- }
- n.kill();
- break;
- }
- }
- return null;
- }
- private void hit(PageRef r) {
- // We don't need to be 100% accurate here. Its sufficient that at least
- // one thread performs the increment. Any other concurrent access at
- // exactly the same time can simply use the same clock value.
- //
- // Consequently we attempt the set, but we don't try to recover should
- // it fail. This is why we don't use getAndIncrement() here.
- //
- final long c = clock.get();
- clock.compareAndSet(c, c + 1);
- r.setLastAccess(c);
- }
- private void evict() {
- while (isFull()) {
- int ptr = rng.nextInt(tableSize);
- Entry old = null;
- int slot = 0;
- for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
- if (tableSize <= ptr)
- ptr = 0;
- for (Entry e = table.get(ptr); e != null; e = e.next) {
- if (e.dead)
- continue;
- if (old == null || e.ref.getLastAccess() < old.ref
- .getLastAccess()) {
- old = e;
- slot = ptr;
- }
- }
- }
- if (old != null) {
- old.kill();
- gc();
- final Entry e1 = table.get(slot);
- table.compareAndSet(slot, e1, clean(e1));
- }
- }
- }
- /**
- * Clear every entry from the cache.
- * <p>
- * This is a last-ditch effort to clear out the cache, such as before it
- * gets replaced by another cache that is configured differently. This
- * method tries to force every cached entry through {@link #clear(PageRef)} to
- * ensure that resources are correctly accounted for and cleaned up by the
- * subclass. A concurrent reader loading entries while this method is
- * running may cause resource accounting failures.
- */
- private void removeAll() {
- for (int s = 0; s < tableSize; s++) {
- Entry e1;
- do {
- e1 = table.get(s);
- for (Entry e = e1; e != null; e = e.next)
- e.kill();
- } while (!table.compareAndSet(s, e1, null));
- }
- gc();
- }
- /**
- * Clear all entries related to a single file.
- * <p>
- * Typically this method is invoked during {@link Pack#close()}, when we
- * know the pack is never going to be useful to us again (for example, it no
- * longer exists on disk). A concurrent reader loading an entry from this
- * same pack may cause the pack to become stuck in the cache anyway.
- *
- * @param pack
- * the file to purge all entries of.
- */
- private void removeAll(Pack pack) {
- for (int s = 0; s < tableSize; s++) {
- final Entry e1 = table.get(s);
- boolean hasDead = false;
- for (Entry e = e1; e != null; e = e.next) {
- if (e.ref.getPack() == pack) {
- e.kill();
- hasDead = true;
- } else if (e.dead)
- hasDead = true;
- }
- if (hasDead)
- table.compareAndSet(s, e1, clean(e1));
- }
- gc();
- }
- private void gc() {
- queue.gc();
- }
- private int slot(Pack pack, long position) {
- return (hash(pack.hash, position) >>> 1) % tableSize;
- }
- private Lock lock(Pack pack, long position) {
- return locks[(hash(pack.hash, position) >>> 1) % locks.length];
- }
- private static Entry clean(Entry top) {
- while (top != null && top.dead) {
- top.ref.kill();
- top = top.next;
- }
- if (top == null)
- return null;
- final Entry n = clean(top.next);
- return n == top.next ? top : new Entry(n, top.ref);
- }
- private static class Entry {
- /** Next entry in the hash table's chain list. */
- final Entry next;
- /** The referenced object. */
- final PageRef<ByteWindow> ref;
- /**
- * Marked true when ref.get() returns null and the ref is dead.
- * <p>
- * A true here indicates that the ref is no longer accessible, and that
- * we therefore need to eventually purge this Entry object out of the
- * bucket's chain.
- */
- volatile boolean dead;
- Entry(Entry n, PageRef<ByteWindow> r) {
- next = n;
- ref = r;
- }
- final void kill() {
- dead = true;
- ref.kill();
- }
- }
- private static interface PageRef<T> {
- /**
- * Returns this reference object's referent. If this reference object
- * has been cleared, either by the program or by the garbage collector,
- * then this method returns <code>null</code>.
- *
- * @return The object to which this reference refers, or
- * <code>null</code> if this reference object has been cleared
- */
- T get();
- /**
- * Kill this ref
- *
- * @return <code>true</code> if this reference object was successfully
- * killed; <code>false</code> if it was already killed
- */
- boolean kill();
- /**
- * Get the {@link org.eclipse.jgit.internal.storage.file.Pack} the
- * referenced cache page is allocated for
- *
- * @return the {@link org.eclipse.jgit.internal.storage.file.Pack} the
- * referenced cache page is allocated for
- */
- Pack getPack();
- /**
- * Get the position of the referenced cache page in the
- * {@link org.eclipse.jgit.internal.storage.file.Pack}
- *
- * @return the position of the referenced cache page in the
- * {@link org.eclipse.jgit.internal.storage.file.Pack}
- */
- long getPosition();
- /**
- * Get size of cache page
- *
- * @return size of cache page
- */
- int getSize();
- /**
- * Get pseudo time of last access to this cache page
- *
- * @return pseudo time of last access to this cache page
- */
- long getLastAccess();
- /**
- * Set pseudo time of last access to this cache page
- *
- * @param time
- * pseudo time of last access to this cache page
- */
- void setLastAccess(long time);
- /**
- * Whether this is a strong reference.
- * @return {@code true} if this is a strong reference
- */
- boolean isStrongRef();
- }
- /** A soft reference wrapped around a cached object. */
- private static class SoftRef extends SoftReference<ByteWindow>
- implements PageRef<ByteWindow> {
- private final Pack pack;
- private final long position;
- private final int size;
- private long lastAccess;
- protected SoftRef(final Pack pack, final long position,
- final ByteWindow v, final SoftCleanupQueue queue) {
- super(v, queue);
- this.pack = pack;
- this.position = position;
- this.size = v.size();
- }
- @Override
- public Pack getPack() {
- return pack;
- }
- @Override
- public long getPosition() {
- return position;
- }
- @Override
- public int getSize() {
- return size;
- }
- @Override
- public long getLastAccess() {
- return lastAccess;
- }
- @Override
- public void setLastAccess(long time) {
- this.lastAccess = time;
- }
- @Override
- public boolean kill() {
- return enqueue();
- }
- @Override
- public boolean isStrongRef() {
- return false;
- }
- }
- /** A strong reference wrapped around a cached object. */
- private static class StrongRef implements PageRef<ByteWindow> {
- private ByteWindow referent;
- private final Pack pack;
- private final long position;
- private final int size;
- private long lastAccess;
- private CleanupQueue queue;
- protected StrongRef(final Pack pack, final long position,
- final ByteWindow v, final CleanupQueue queue) {
- this.pack = pack;
- this.position = position;
- this.referent = v;
- this.size = v.size();
- this.queue = queue;
- }
- @Override
- public Pack getPack() {
- return pack;
- }
- @Override
- public long getPosition() {
- return position;
- }
- @Override
- public int getSize() {
- return size;
- }
- @Override
- public long getLastAccess() {
- return lastAccess;
- }
- @Override
- public void setLastAccess(long time) {
- this.lastAccess = time;
- }
- @Override
- public ByteWindow get() {
- return referent;
- }
- @Override
- public boolean kill() {
- if (referent == null) {
- return false;
- }
- referent = null;
- return queue.enqueue(this);
- }
- @Override
- public boolean isStrongRef() {
- return true;
- }
- }
- private static interface CleanupQueue {
- boolean enqueue(PageRef<ByteWindow> r);
- void gc();
- }
- private static class SoftCleanupQueue extends ReferenceQueue<ByteWindow>
- implements CleanupQueue {
- private final WindowCache wc;
- SoftCleanupQueue(WindowCache cache) {
- this.wc = cache;
- }
- @Override
- public boolean enqueue(PageRef<ByteWindow> r) {
- // no need to explicitly add soft references which are enqueued by
- // the JVM
- return false;
- }
- @Override
- public void gc() {
- SoftRef r;
- while ((r = (SoftRef) poll()) != null) {
- wc.clear(r);
- final int s = wc.slot(r.getPack(), r.getPosition());
- final Entry e1 = wc.table.get(s);
- for (Entry n = e1; n != null; n = n.next) {
- if (n.ref == r) {
- n.dead = true;
- wc.table.compareAndSet(s, e1, clean(e1));
- break;
- }
- }
- }
- }
- }
- private static class StrongCleanupQueue implements CleanupQueue {
- private final WindowCache wc;
- private final ConcurrentLinkedQueue<PageRef<ByteWindow>> queue = new ConcurrentLinkedQueue<>();
- StrongCleanupQueue(WindowCache wc) {
- this.wc = wc;
- }
- @Override
- public boolean enqueue(PageRef<ByteWindow> r) {
- if (queue.contains(r)) {
- return false;
- }
- return queue.add(r);
- }
- @Override
- public void gc() {
- PageRef<ByteWindow> r;
- while ((r = queue.poll()) != null) {
- wc.clear(r);
- final int s = wc.slot(r.getPack(), r.getPosition());
- final Entry e1 = wc.table.get(s);
- for (Entry n = e1; n != null; n = n.next) {
- if (n.ref == r) {
- n.dead = true;
- wc.table.compareAndSet(s, e1, clean(e1));
- break;
- }
- }
- }
- }
- }
- private static final class Lock {
- // Used only for its implicit monitor.
- }
- }