You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WindowCache.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035
  1. /*
  2. * Copyright (C) 2008-2009, Google Inc.
  3. * Copyright (C) 2008, 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.AtomicLong;
  21. import java.util.concurrent.atomic.AtomicReferenceArray;
  22. import java.util.concurrent.atomic.LongAdder;
  23. import java.util.concurrent.locks.ReentrantLock;
  24. import java.util.stream.Collectors;
  25. import org.eclipse.jgit.annotations.NonNull;
  26. import org.eclipse.jgit.internal.JGitText;
  27. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  28. import org.eclipse.jgit.storage.file.WindowCacheStats;
  29. import org.eclipse.jgit.util.Monitoring;
  30. /**
  31. * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in
  32. * memory for faster read access.
  33. * <p>
  34. * The WindowCache serves as a Java based "buffer cache", loading segments of a
  35. * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
  36. * only tiny slices of a file, the WindowCache tries to smooth out these tiny
  37. * reads into larger block-sized IO operations.
  38. * <p>
  39. * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by
  40. * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
  41. * This is ensured by an array of locks, with the tuple hashed to a lock
  42. * instance.
  43. * <p>
  44. * During a miss, older entries are evicted from the cache so long as
  45. * {@link #isFull()} returns true.
  46. * <p>
  47. * Its too expensive during object access to be 100% accurate with a least
  48. * recently used (LRU) algorithm. Strictly ordering every read is a lot of
  49. * overhead that typically doesn't yield a corresponding benefit to the
  50. * application.
  51. * <p>
  52. * This cache implements a loose LRU policy by randomly picking a window
  53. * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
  54. * within that window.
  55. * <p>
  56. * Entities created by the cache are held under SoftReferences if option
  57. * {@code core.packedGitUseStrongRefs} is set to {@code false} in the git config
  58. * (this is the default) or by calling
  59. * {@link WindowCacheConfig#setPackedGitUseStrongRefs(boolean)}, permitting the
  60. * Java runtime's garbage collector to evict entries when heap memory gets low.
  61. * Most JREs implement a loose least recently used algorithm for this eviction.
  62. * When this option is set to {@code true} strong references are used which
  63. * means that Java gc cannot evict the WindowCache to reclaim memory. On the
  64. * other hand this provides more predictable performance since the cache isn't
  65. * flushed when used heap comes close to the maximum heap size.
  66. * <p>
  67. * The internal hash table does not expand at runtime, instead it is fixed in
  68. * size at cache creation time. The internal lock table used to gate load
  69. * invocations is also fixed in size.
  70. * <p>
  71. * The key tuple is passed through to methods as a pair of parameters rather
  72. * than as a single Object, thus reducing the transient memory allocations of
  73. * callers. It is more efficient to avoid the allocation, as we can't be 100%
  74. * sure that a JIT would be able to stack-allocate a key tuple.
  75. * <p>
  76. * This cache has an implementation rule such that:
  77. * <ul>
  78. * <li>{@link #load(PackFile, long)} is invoked by at most one thread at a time
  79. * for a given <code>(PackFile,position)</code> tuple.</li>
  80. * <li>For every <code>load()</code> invocation there is exactly one
  81. * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a
  82. * SoftReference or a StrongReference around the cached entity.</li>
  83. * <li>For every Reference created by <code>createRef()</code> there will be
  84. * exactly one call to {@link #clear(PageRef)} to cleanup any resources associated
  85. * with the (now expired) cached entity.</li>
  86. * </ul>
  87. * <p>
  88. * Therefore, it is safe to perform resource accounting increments during the
  89. * {@link #load(PackFile, long)} or
  90. * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching
  91. * decrements during {@link #clear(PageRef)}. Implementors may need to override
  92. * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional
  93. * accounting information into an implementation specific
  94. * {@link org.eclipse.jgit.internal.storage.file.WindowCache.PageRef} subclass, as
  95. * the cached entity may have already been evicted by the JRE's garbage
  96. * collector.
  97. * <p>
  98. * To maintain higher concurrency workloads, during eviction only one thread
  99. * performs the eviction work, while other threads can continue to insert new
  100. * objects in parallel. This means that the cache can be temporarily over limit,
  101. * especially if the nominated eviction thread is being starved relative to the
  102. * other threads.
  103. */
  104. public class WindowCache {
  105. /**
  106. * Record statistics for a cache
  107. */
  108. static interface StatsRecorder {
  109. /**
  110. * Record cache hits. Called when cache returns a cached entry.
  111. *
  112. * @param count
  113. * number of cache hits to record
  114. */
  115. void recordHits(int count);
  116. /**
  117. * Record cache misses. Called when the cache returns an entry which had
  118. * to be loaded.
  119. *
  120. * @param count
  121. * number of cache misses to record
  122. */
  123. void recordMisses(int count);
  124. /**
  125. * Record a successful load of a cache entry
  126. *
  127. * @param loadTimeNanos
  128. * time to load a cache entry
  129. */
  130. void recordLoadSuccess(long loadTimeNanos);
  131. /**
  132. * Record a failed load of a cache entry
  133. *
  134. * @param loadTimeNanos
  135. * time used trying to load a cache entry
  136. */
  137. void recordLoadFailure(long loadTimeNanos);
  138. /**
  139. * Record cache evictions due to the cache evictions strategy
  140. *
  141. * @param count
  142. * number of evictions to record
  143. */
  144. void recordEvictions(int count);
  145. /**
  146. * Record files opened by cache
  147. *
  148. * @param delta
  149. * delta of number of files opened by cache
  150. */
  151. void recordOpenFiles(int delta);
  152. /**
  153. * Record cached bytes
  154. *
  155. * @param pack
  156. * pack file the bytes are read from
  157. *
  158. * @param delta
  159. * delta of cached bytes
  160. */
  161. void recordOpenBytes(PackFile pack, int delta);
  162. /**
  163. * Returns a snapshot of this recorder's stats. Note that this may be an
  164. * inconsistent view, as it may be interleaved with update operations.
  165. *
  166. * @return a snapshot of this recorder's stats
  167. */
  168. @NonNull
  169. WindowCacheStats getStats();
  170. }
  171. static class StatsRecorderImpl
  172. implements StatsRecorder, WindowCacheStats {
  173. private final LongAdder hitCount;
  174. private final LongAdder missCount;
  175. private final LongAdder loadSuccessCount;
  176. private final LongAdder loadFailureCount;
  177. private final LongAdder totalLoadTime;
  178. private final LongAdder evictionCount;
  179. private final LongAdder openFileCount;
  180. private final LongAdder openByteCount;
  181. private final Map<String, LongAdder> openByteCountPerRepository;
  182. /**
  183. * Constructs an instance with all counts initialized to zero.
  184. */
  185. public StatsRecorderImpl() {
  186. hitCount = new LongAdder();
  187. missCount = new LongAdder();
  188. loadSuccessCount = new LongAdder();
  189. loadFailureCount = new LongAdder();
  190. totalLoadTime = new LongAdder();
  191. evictionCount = new LongAdder();
  192. openFileCount = new LongAdder();
  193. openByteCount = new LongAdder();
  194. openByteCountPerRepository = new ConcurrentHashMap<>();
  195. }
  196. @Override
  197. public void recordHits(int count) {
  198. hitCount.add(count);
  199. }
  200. @Override
  201. public void recordMisses(int count) {
  202. missCount.add(count);
  203. }
  204. @Override
  205. public void recordLoadSuccess(long loadTimeNanos) {
  206. loadSuccessCount.increment();
  207. totalLoadTime.add(loadTimeNanos);
  208. }
  209. @Override
  210. public void recordLoadFailure(long loadTimeNanos) {
  211. loadFailureCount.increment();
  212. totalLoadTime.add(loadTimeNanos);
  213. }
  214. @Override
  215. public void recordEvictions(int count) {
  216. evictionCount.add(count);
  217. }
  218. @Override
  219. public void recordOpenFiles(int delta) {
  220. openFileCount.add(delta);
  221. }
  222. @Override
  223. public void recordOpenBytes(PackFile pack, int delta) {
  224. openByteCount.add(delta);
  225. String repositoryId = repositoryId(pack);
  226. LongAdder la = openByteCountPerRepository
  227. .computeIfAbsent(repositoryId, k -> new LongAdder());
  228. la.add(delta);
  229. if (delta < 0) {
  230. openByteCountPerRepository.computeIfPresent(repositoryId,
  231. (k, v) -> v.longValue() == 0 ? null : v);
  232. }
  233. }
  234. private static String repositoryId(PackFile pack) {
  235. // use repository's gitdir since packfile doesn't know its
  236. // 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;
  339. }
  340. static final ByteWindow get(PackFile 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) {
  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(PackFile 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 boolean useStrongRefs;
  379. private WindowCache(WindowCacheConfig cfg) {
  380. tableSize = tableSize(cfg);
  381. final int lockCount = lockCount(cfg);
  382. if (tableSize < 1)
  383. throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
  384. if (lockCount < 1)
  385. throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
  386. clock = new AtomicLong(1);
  387. table = new AtomicReferenceArray<>(tableSize);
  388. locks = new Lock[lockCount];
  389. for (int i = 0; i < locks.length; i++)
  390. locks[i] = new Lock();
  391. evictLock = new ReentrantLock();
  392. int eb = (int) (tableSize * .1);
  393. if (64 < eb)
  394. eb = 64;
  395. else if (eb < 4)
  396. eb = 4;
  397. if (tableSize < eb)
  398. eb = tableSize;
  399. evictBatch = eb;
  400. maxFiles = cfg.getPackedGitOpenFiles();
  401. maxBytes = cfg.getPackedGitLimit();
  402. mmap = cfg.isPackedGitMMAP();
  403. windowSizeShift = bits(cfg.getPackedGitWindowSize());
  404. windowSize = 1 << windowSizeShift;
  405. useStrongRefs = cfg.isPackedGitUseStrongRefs();
  406. queue = useStrongRefs ? new StrongCleanupQueue(this)
  407. : new SoftCleanupQueue(this);
  408. mbean = new StatsRecorderImpl();
  409. statsRecorder = mbean;
  410. Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
  411. if (maxFiles < 1)
  412. throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
  413. if (maxBytes < windowSize)
  414. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  415. }
  416. /**
  417. * @return cache statistics for the WindowCache
  418. */
  419. public WindowCacheStats getStats() {
  420. return statsRecorder.getStats();
  421. }
  422. /**
  423. * Reset stats. Does not reset open bytes and open files stats.
  424. */
  425. public void resetStats() {
  426. mbean.resetCounters();
  427. }
  428. private int hash(int packHash, long off) {
  429. return packHash + (int) (off >>> windowSizeShift);
  430. }
  431. private ByteWindow load(PackFile pack, long offset) throws IOException {
  432. long startTime = System.nanoTime();
  433. if (pack.beginWindowCache())
  434. statsRecorder.recordOpenFiles(1);
  435. try {
  436. if (mmap)
  437. return pack.mmap(offset, windowSize);
  438. ByteArrayWindow w = pack.read(offset, windowSize);
  439. statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
  440. return w;
  441. } catch (IOException | RuntimeException | Error e) {
  442. close(pack);
  443. statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
  444. throw e;
  445. } finally {
  446. statsRecorder.recordMisses(1);
  447. }
  448. }
  449. private PageRef<ByteWindow> createRef(PackFile p, long o, ByteWindow v) {
  450. final PageRef<ByteWindow> ref = useStrongRefs
  451. ? new StrongRef(p, o, v, queue)
  452. : new SoftRef(p, o, v, (SoftCleanupQueue) queue);
  453. statsRecorder.recordOpenBytes(ref.getPack(), ref.getSize());
  454. return ref;
  455. }
  456. private void clear(PageRef<ByteWindow> ref) {
  457. statsRecorder.recordOpenBytes(ref.getPack(), -ref.getSize());
  458. statsRecorder.recordEvictions(1);
  459. close(ref.getPack());
  460. }
  461. private void close(PackFile pack) {
  462. if (pack.endWindowCache()) {
  463. statsRecorder.recordOpenFiles(-1);
  464. }
  465. }
  466. private boolean isFull() {
  467. return maxFiles < mbean.getOpenFileCount()
  468. || maxBytes < mbean.getOpenByteCount();
  469. }
  470. private long toStart(long offset) {
  471. return (offset >>> windowSizeShift) << windowSizeShift;
  472. }
  473. private static int tableSize(WindowCacheConfig cfg) {
  474. final int wsz = cfg.getPackedGitWindowSize();
  475. final long limit = cfg.getPackedGitLimit();
  476. if (wsz <= 0)
  477. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  478. if (limit < wsz)
  479. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  480. return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
  481. }
  482. private static int lockCount(WindowCacheConfig cfg) {
  483. return Math.max(cfg.getPackedGitOpenFiles(), 32);
  484. }
  485. /**
  486. * Lookup a cached object, creating and loading it if it doesn't exist.
  487. *
  488. * @param pack
  489. * the pack that "contains" the cached object.
  490. * @param position
  491. * offset within <code>pack</code> of the object.
  492. * @return the object reference.
  493. * @throws IOException
  494. * the object reference was not in the cache and could not be
  495. * obtained by {@link #load(PackFile, long)}.
  496. */
  497. private ByteWindow getOrLoad(PackFile pack, long position)
  498. throws IOException {
  499. final int slot = slot(pack, position);
  500. final Entry e1 = table.get(slot);
  501. ByteWindow v = scan(e1, pack, position);
  502. if (v != null) {
  503. statsRecorder.recordHits(1);
  504. return v;
  505. }
  506. synchronized (lock(pack, position)) {
  507. Entry e2 = table.get(slot);
  508. if (e2 != e1) {
  509. v = scan(e2, pack, position);
  510. if (v != null) {
  511. statsRecorder.recordHits(1);
  512. return v;
  513. }
  514. }
  515. v = load(pack, position);
  516. final PageRef<ByteWindow> ref = createRef(pack, position, v);
  517. hit(ref);
  518. for (;;) {
  519. final Entry n = new Entry(clean(e2), ref);
  520. if (table.compareAndSet(slot, e2, n))
  521. break;
  522. e2 = table.get(slot);
  523. }
  524. }
  525. if (evictLock.tryLock()) {
  526. try {
  527. gc();
  528. evict();
  529. } finally {
  530. evictLock.unlock();
  531. }
  532. }
  533. return v;
  534. }
  535. private ByteWindow scan(Entry n, PackFile pack, long position) {
  536. for (; n != null; n = n.next) {
  537. final PageRef<ByteWindow> r = n.ref;
  538. if (r.getPack() == pack && r.getPosition() == position) {
  539. final ByteWindow v = r.get();
  540. if (v != null) {
  541. hit(r);
  542. return v;
  543. }
  544. n.kill();
  545. break;
  546. }
  547. }
  548. return null;
  549. }
  550. private void hit(PageRef r) {
  551. // We don't need to be 100% accurate here. Its sufficient that at least
  552. // one thread performs the increment. Any other concurrent access at
  553. // exactly the same time can simply use the same clock value.
  554. //
  555. // Consequently we attempt the set, but we don't try to recover should
  556. // it fail. This is why we don't use getAndIncrement() here.
  557. //
  558. final long c = clock.get();
  559. clock.compareAndSet(c, c + 1);
  560. r.setLastAccess(c);
  561. }
  562. private void evict() {
  563. while (isFull()) {
  564. int ptr = rng.nextInt(tableSize);
  565. Entry old = null;
  566. int slot = 0;
  567. for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
  568. if (tableSize <= ptr)
  569. ptr = 0;
  570. for (Entry e = table.get(ptr); e != null; e = e.next) {
  571. if (e.dead)
  572. continue;
  573. if (old == null || e.ref.getLastAccess() < old.ref
  574. .getLastAccess()) {
  575. old = e;
  576. slot = ptr;
  577. }
  578. }
  579. }
  580. if (old != null) {
  581. old.kill();
  582. gc();
  583. final Entry e1 = table.get(slot);
  584. table.compareAndSet(slot, e1, clean(e1));
  585. }
  586. }
  587. }
  588. /**
  589. * Clear every entry from the cache.
  590. * <p>
  591. * This is a last-ditch effort to clear out the cache, such as before it
  592. * gets replaced by another cache that is configured differently. This
  593. * method tries to force every cached entry through {@link #clear(PageRef)} to
  594. * ensure that resources are correctly accounted for and cleaned up by the
  595. * subclass. A concurrent reader loading entries while this method is
  596. * running may cause resource accounting failures.
  597. */
  598. private void removeAll() {
  599. for (int s = 0; s < tableSize; s++) {
  600. Entry e1;
  601. do {
  602. e1 = table.get(s);
  603. for (Entry e = e1; e != null; e = e.next)
  604. e.kill();
  605. } while (!table.compareAndSet(s, e1, null));
  606. }
  607. gc();
  608. }
  609. /**
  610. * Clear all entries related to a single file.
  611. * <p>
  612. * Typically this method is invoked during {@link PackFile#close()}, when we
  613. * know the pack is never going to be useful to us again (for example, it no
  614. * longer exists on disk). A concurrent reader loading an entry from this
  615. * same pack may cause the pack to become stuck in the cache anyway.
  616. *
  617. * @param pack
  618. * the file to purge all entries of.
  619. */
  620. private void removeAll(PackFile pack) {
  621. for (int s = 0; s < tableSize; s++) {
  622. final Entry e1 = table.get(s);
  623. boolean hasDead = false;
  624. for (Entry e = e1; e != null; e = e.next) {
  625. if (e.ref.getPack() == pack) {
  626. e.kill();
  627. hasDead = true;
  628. } else if (e.dead)
  629. hasDead = true;
  630. }
  631. if (hasDead)
  632. table.compareAndSet(s, e1, clean(e1));
  633. }
  634. gc();
  635. }
  636. private void gc() {
  637. queue.gc();
  638. }
  639. private int slot(PackFile pack, long position) {
  640. return (hash(pack.hash, position) >>> 1) % tableSize;
  641. }
  642. private Lock lock(PackFile pack, long position) {
  643. return locks[(hash(pack.hash, position) >>> 1) % locks.length];
  644. }
  645. private static Entry clean(Entry top) {
  646. while (top != null && top.dead) {
  647. top.ref.kill();
  648. top = top.next;
  649. }
  650. if (top == null)
  651. return null;
  652. final Entry n = clean(top.next);
  653. return n == top.next ? top : new Entry(n, top.ref);
  654. }
  655. private static class Entry {
  656. /** Next entry in the hash table's chain list. */
  657. final Entry next;
  658. /** The referenced object. */
  659. final PageRef<ByteWindow> ref;
  660. /**
  661. * Marked true when ref.get() returns null and the ref is dead.
  662. * <p>
  663. * A true here indicates that the ref is no longer accessible, and that
  664. * we therefore need to eventually purge this Entry object out of the
  665. * bucket's chain.
  666. */
  667. volatile boolean dead;
  668. Entry(Entry n, PageRef<ByteWindow> r) {
  669. next = n;
  670. ref = r;
  671. }
  672. final void kill() {
  673. dead = true;
  674. ref.kill();
  675. }
  676. }
  677. private static interface PageRef<T> {
  678. /**
  679. * Returns this reference object's referent. If this reference object
  680. * has been cleared, either by the program or by the garbage collector,
  681. * then this method returns <code>null</code>.
  682. *
  683. * @return The object to which this reference refers, or
  684. * <code>null</code> if this reference object has been cleared
  685. */
  686. T get();
  687. /**
  688. * Kill this ref
  689. *
  690. * @return <code>true</code> if this reference object was successfully
  691. * killed; <code>false</code> if it was already killed
  692. */
  693. boolean kill();
  694. /**
  695. * Get the packfile the referenced cache page is allocated for
  696. *
  697. * @return the packfile the referenced cache page is allocated for
  698. */
  699. PackFile getPack();
  700. /**
  701. * Get the position of the referenced cache page in the packfile
  702. *
  703. * @return the position of the referenced cache page in the packfile
  704. */
  705. long getPosition();
  706. /**
  707. * Get size of cache page
  708. *
  709. * @return size of cache page
  710. */
  711. int getSize();
  712. /**
  713. * Get pseudo time of last access to this cache page
  714. *
  715. * @return pseudo time of last access to this cache page
  716. */
  717. long getLastAccess();
  718. /**
  719. * Set pseudo time of last access to this cache page
  720. *
  721. * @param time
  722. * pseudo time of last access to this cache page
  723. */
  724. void setLastAccess(long time);
  725. /**
  726. * Whether this is a strong reference.
  727. * @return {@code true} if this is a strong reference
  728. */
  729. boolean isStrongRef();
  730. }
  731. /** A soft reference wrapped around a cached object. */
  732. private static class SoftRef extends SoftReference<ByteWindow>
  733. implements PageRef<ByteWindow> {
  734. private final PackFile pack;
  735. private final long position;
  736. private final int size;
  737. private long lastAccess;
  738. protected SoftRef(final PackFile pack, final long position,
  739. final ByteWindow v, final SoftCleanupQueue queue) {
  740. super(v, queue);
  741. this.pack = pack;
  742. this.position = position;
  743. this.size = v.size();
  744. }
  745. @Override
  746. public PackFile getPack() {
  747. return pack;
  748. }
  749. @Override
  750. public long getPosition() {
  751. return position;
  752. }
  753. @Override
  754. public int getSize() {
  755. return size;
  756. }
  757. @Override
  758. public long getLastAccess() {
  759. return lastAccess;
  760. }
  761. @Override
  762. public void setLastAccess(long time) {
  763. this.lastAccess = time;
  764. }
  765. @Override
  766. public boolean kill() {
  767. return enqueue();
  768. }
  769. @Override
  770. public boolean isStrongRef() {
  771. return false;
  772. }
  773. }
  774. /** A strong reference wrapped around a cached object. */
  775. private static class StrongRef implements PageRef<ByteWindow> {
  776. private ByteWindow referent;
  777. private final PackFile pack;
  778. private final long position;
  779. private final int size;
  780. private long lastAccess;
  781. private CleanupQueue queue;
  782. protected StrongRef(final PackFile pack, final long position,
  783. final ByteWindow v, final CleanupQueue queue) {
  784. this.pack = pack;
  785. this.position = position;
  786. this.referent = v;
  787. this.size = v.size();
  788. this.queue = queue;
  789. }
  790. @Override
  791. public PackFile getPack() {
  792. return pack;
  793. }
  794. @Override
  795. public long getPosition() {
  796. return position;
  797. }
  798. @Override
  799. public int getSize() {
  800. return size;
  801. }
  802. @Override
  803. public long getLastAccess() {
  804. return lastAccess;
  805. }
  806. @Override
  807. public void setLastAccess(long time) {
  808. this.lastAccess = time;
  809. }
  810. @Override
  811. public ByteWindow get() {
  812. return referent;
  813. }
  814. @Override
  815. public boolean kill() {
  816. if (referent == null) {
  817. return false;
  818. }
  819. referent = null;
  820. return queue.enqueue(this);
  821. }
  822. @Override
  823. public boolean isStrongRef() {
  824. return true;
  825. }
  826. }
  827. private static interface CleanupQueue {
  828. boolean enqueue(PageRef<ByteWindow> r);
  829. void gc();
  830. }
  831. private static class SoftCleanupQueue extends ReferenceQueue<ByteWindow>
  832. implements CleanupQueue {
  833. private final WindowCache wc;
  834. SoftCleanupQueue(WindowCache cache) {
  835. this.wc = cache;
  836. }
  837. @Override
  838. public boolean enqueue(PageRef<ByteWindow> r) {
  839. // no need to explicitly add soft references which are enqueued by
  840. // the JVM
  841. return false;
  842. }
  843. @Override
  844. public void gc() {
  845. SoftRef r;
  846. while ((r = (SoftRef) poll()) != null) {
  847. wc.clear(r);
  848. final int s = wc.slot(r.getPack(), r.getPosition());
  849. final Entry e1 = wc.table.get(s);
  850. for (Entry n = e1; n != null; n = n.next) {
  851. if (n.ref == r) {
  852. n.dead = true;
  853. wc.table.compareAndSet(s, e1, clean(e1));
  854. break;
  855. }
  856. }
  857. }
  858. }
  859. }
  860. private static class StrongCleanupQueue implements CleanupQueue {
  861. private final WindowCache wc;
  862. private final ConcurrentLinkedQueue<PageRef<ByteWindow>> queue = new ConcurrentLinkedQueue<>();
  863. StrongCleanupQueue(WindowCache wc) {
  864. this.wc = wc;
  865. }
  866. @Override
  867. public boolean enqueue(PageRef<ByteWindow> r) {
  868. if (queue.contains(r)) {
  869. return false;
  870. }
  871. return queue.add(r);
  872. }
  873. @Override
  874. public void gc() {
  875. PageRef<ByteWindow> r;
  876. while ((r = queue.poll()) != null) {
  877. wc.clear(r);
  878. final int s = wc.slot(r.getPack(), r.getPosition());
  879. final Entry e1 = wc.table.get(s);
  880. for (Entry n = e1; n != null; n = n.next) {
  881. if (n.ref == r) {
  882. n.dead = true;
  883. wc.table.compareAndSet(s, e1, clean(e1));
  884. break;
  885. }
  886. }
  887. }
  888. }
  889. }
  890. private static final class Lock {
  891. // Used only for its implicit monitor.
  892. }
  893. }