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 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /*
  2. * Copyright (C) 2008-2009, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4. * and other copyright owners as documented in the project's IP log.
  5. *
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Eclipse Distribution License v1.0 which
  8. * accompanies this distribution, is reproduced below, and is
  9. * available at http://www.eclipse.org/org/documents/edl-v10.php
  10. *
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or
  14. * without modification, are permitted provided that the following
  15. * conditions are met:
  16. *
  17. * - Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * - Redistributions in binary form must reproduce the above
  21. * copyright notice, this list of conditions and the following
  22. * disclaimer in the documentation and/or other materials provided
  23. * with the distribution.
  24. *
  25. * - Neither the name of the Eclipse Foundation, Inc. nor the
  26. * names of its contributors may be used to endorse or promote
  27. * products derived from this software without specific prior
  28. * written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. */
  44. package org.eclipse.jgit.internal.storage.file;
  45. import java.io.IOException;
  46. import java.lang.ref.ReferenceQueue;
  47. import java.lang.ref.SoftReference;
  48. import java.util.Random;
  49. import java.util.concurrent.atomic.AtomicLong;
  50. import java.util.concurrent.atomic.AtomicReferenceArray;
  51. import java.util.concurrent.atomic.LongAdder;
  52. import java.util.concurrent.locks.ReentrantLock;
  53. import org.eclipse.jgit.annotations.NonNull;
  54. import org.eclipse.jgit.internal.JGitText;
  55. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  56. import org.eclipse.jgit.storage.file.WindowCacheStats;
  57. import org.eclipse.jgit.util.Monitoring;
  58. /**
  59. * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in
  60. * memory for faster read access.
  61. * <p>
  62. * The WindowCache serves as a Java based "buffer cache", loading segments of a
  63. * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
  64. * only tiny slices of a file, the WindowCache tries to smooth out these tiny
  65. * reads into larger block-sized IO operations.
  66. * <p>
  67. * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by
  68. * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
  69. * This is ensured by an array of locks, with the tuple hashed to a lock
  70. * instance.
  71. * <p>
  72. * During a miss, older entries are evicted from the cache so long as
  73. * {@link #isFull()} returns true.
  74. * <p>
  75. * Its too expensive during object access to be 100% accurate with a least
  76. * recently used (LRU) algorithm. Strictly ordering every read is a lot of
  77. * overhead that typically doesn't yield a corresponding benefit to the
  78. * application.
  79. * <p>
  80. * This cache implements a loose LRU policy by randomly picking a window
  81. * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
  82. * within that window.
  83. * <p>
  84. * Entities created by the cache are held under SoftReferences, permitting the
  85. * Java runtime's garbage collector to evict entries when heap memory gets low.
  86. * Most JREs implement a loose least recently used algorithm for this eviction.
  87. * <p>
  88. * The internal hash table does not expand at runtime, instead it is fixed in
  89. * size at cache creation time. The internal lock table used to gate load
  90. * invocations is also fixed in size.
  91. * <p>
  92. * The key tuple is passed through to methods as a pair of parameters rather
  93. * than as a single Object, thus reducing the transient memory allocations of
  94. * callers. It is more efficient to avoid the allocation, as we can't be 100%
  95. * sure that a JIT would be able to stack-allocate a key tuple.
  96. * <p>
  97. * This cache has an implementation rule such that:
  98. * <ul>
  99. * <li>{@link #load(PackFile, long)} is invoked by at most one thread at a time
  100. * for a given <code>(PackFile,position)</code> tuple.</li>
  101. * <li>For every <code>load()</code> invocation there is exactly one
  102. * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a
  103. * SoftReference around the cached entity.</li>
  104. * <li>For every Reference created by <code>createRef()</code> there will be
  105. * exactly one call to {@link #clear(Ref)} to cleanup any resources associated
  106. * with the (now expired) cached entity.</li>
  107. * </ul>
  108. * <p>
  109. * Therefore, it is safe to perform resource accounting increments during the
  110. * {@link #load(PackFile, long)} or
  111. * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching
  112. * decrements during {@link #clear(Ref)}. Implementors may need to override
  113. * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional
  114. * accounting information into an implementation specific
  115. * {@link org.eclipse.jgit.internal.storage.file.WindowCache.Ref} subclass, as
  116. * the cached entity may have already been evicted by the JRE's garbage
  117. * collector.
  118. * <p>
  119. * To maintain higher concurrency workloads, during eviction only one thread
  120. * performs the eviction work, while other threads can continue to insert new
  121. * objects in parallel. This means that the cache can be temporarily over limit,
  122. * especially if the nominated eviction thread is being starved relative to the
  123. * other threads.
  124. */
  125. public class WindowCache {
  126. /**
  127. * Record statistics for a cache
  128. */
  129. static interface StatsRecorder {
  130. /**
  131. * Record cache hits. Called when cache returns a cached entry.
  132. *
  133. * @param count
  134. * number of cache hits to record
  135. */
  136. void recordHits(int count);
  137. /**
  138. * Record cache misses. Called when the cache returns an entry which had
  139. * to be loaded.
  140. *
  141. * @param count
  142. * number of cache misses to record
  143. */
  144. void recordMisses(int count);
  145. /**
  146. * Record a successful load of a cache entry
  147. *
  148. * @param loadTimeNanos
  149. * time to load a cache entry
  150. */
  151. void recordLoadSuccess(long loadTimeNanos);
  152. /**
  153. * Record a failed load of a cache entry
  154. *
  155. * @param loadTimeNanos
  156. * time used trying to load a cache entry
  157. */
  158. void recordLoadFailure(long loadTimeNanos);
  159. /**
  160. * Record cache evictions due to the cache evictions strategy
  161. *
  162. * @param count
  163. * number of evictions to record
  164. */
  165. void recordEvictions(int count);
  166. /**
  167. * Record files opened by cache
  168. *
  169. * @param count
  170. * delta of number of files opened by cache
  171. */
  172. void recordOpenFiles(int count);
  173. /**
  174. * Record cached bytes
  175. *
  176. * @param count
  177. * delta of cached bytes
  178. */
  179. void recordOpenBytes(int count);
  180. /**
  181. * Returns a snapshot of this recorder's stats. Note that this may be an
  182. * inconsistent view, as it may be interleaved with update operations.
  183. *
  184. * @return a snapshot of this recorder's stats
  185. */
  186. @NonNull
  187. WindowCacheStats getStats();
  188. }
  189. static class StatsRecorderImpl
  190. implements StatsRecorder, WindowCacheStats {
  191. private final LongAdder hitCount;
  192. private final LongAdder missCount;
  193. private final LongAdder loadSuccessCount;
  194. private final LongAdder loadFailureCount;
  195. private final LongAdder totalLoadTime;
  196. private final LongAdder evictionCount;
  197. private final LongAdder openFileCount;
  198. private final LongAdder openByteCount;
  199. /**
  200. * Constructs an instance with all counts initialized to zero.
  201. */
  202. public StatsRecorderImpl() {
  203. hitCount = new LongAdder();
  204. missCount = new LongAdder();
  205. loadSuccessCount = new LongAdder();
  206. loadFailureCount = new LongAdder();
  207. totalLoadTime = new LongAdder();
  208. evictionCount = new LongAdder();
  209. openFileCount = new LongAdder();
  210. openByteCount = new LongAdder();
  211. }
  212. @Override
  213. public void recordHits(int count) {
  214. hitCount.add(count);
  215. }
  216. @Override
  217. public void recordMisses(int count) {
  218. missCount.add(count);
  219. }
  220. @Override
  221. public void recordLoadSuccess(long loadTimeNanos) {
  222. loadSuccessCount.increment();
  223. totalLoadTime.add(loadTimeNanos);
  224. }
  225. @Override
  226. public void recordLoadFailure(long loadTimeNanos) {
  227. loadFailureCount.increment();
  228. totalLoadTime.add(loadTimeNanos);
  229. }
  230. @Override
  231. public void recordEvictions(int count) {
  232. evictionCount.add(count);
  233. }
  234. @Override
  235. public void recordOpenFiles(int count) {
  236. openFileCount.add(count);
  237. }
  238. @Override
  239. public void recordOpenBytes(int count) {
  240. openByteCount.add(count);
  241. }
  242. @Override
  243. public WindowCacheStats getStats() {
  244. return this;
  245. }
  246. @Override
  247. public long getHitCount() {
  248. return hitCount.sum();
  249. }
  250. @Override
  251. public long getMissCount() {
  252. return missCount.sum();
  253. }
  254. @Override
  255. public long getLoadSuccessCount() {
  256. return loadSuccessCount.sum();
  257. }
  258. @Override
  259. public long getLoadFailureCount() {
  260. return loadFailureCount.sum();
  261. }
  262. @Override
  263. public long getEvictionCount() {
  264. return evictionCount.sum();
  265. }
  266. @Override
  267. public long getTotalLoadTime() {
  268. return totalLoadTime.sum();
  269. }
  270. @Override
  271. public long getOpenFileCount() {
  272. return openFileCount.sum();
  273. }
  274. @Override
  275. public long getOpenByteCount() {
  276. return openByteCount.sum();
  277. }
  278. @Override
  279. public void resetCounters() {
  280. hitCount.reset();
  281. missCount.reset();
  282. loadSuccessCount.reset();
  283. loadFailureCount.reset();
  284. totalLoadTime.reset();
  285. evictionCount.reset();
  286. }
  287. }
  288. private static final int bits(int newSize) {
  289. if (newSize < 4096)
  290. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  291. if (Integer.bitCount(newSize) != 1)
  292. throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
  293. return Integer.numberOfTrailingZeros(newSize);
  294. }
  295. private static final Random rng = new Random();
  296. private static volatile WindowCache cache;
  297. private static volatile int streamFileThreshold;
  298. static {
  299. reconfigure(new WindowCacheConfig());
  300. }
  301. /**
  302. * Modify the configuration of the window cache.
  303. * <p>
  304. * The new configuration is applied immediately. If the new limits are
  305. * smaller than what what is currently cached, older entries will be purged
  306. * as soon as possible to allow the cache to meet the new limit.
  307. *
  308. * @deprecated use {@code cfg.install()} to avoid internal reference.
  309. * @param cfg
  310. * the new window cache configuration.
  311. * @throws java.lang.IllegalArgumentException
  312. * the cache configuration contains one or more invalid
  313. * settings, usually too low of a limit.
  314. */
  315. @Deprecated
  316. public static void reconfigure(WindowCacheConfig cfg) {
  317. final WindowCache nc = new WindowCache(cfg);
  318. final WindowCache oc = cache;
  319. if (oc != null)
  320. oc.removeAll();
  321. cache = nc;
  322. streamFileThreshold = cfg.getStreamFileThreshold();
  323. DeltaBaseCache.reconfigure(cfg);
  324. }
  325. static int getStreamFileThreshold() {
  326. return streamFileThreshold;
  327. }
  328. /**
  329. * @return the cached instance.
  330. */
  331. public static WindowCache getInstance() {
  332. return cache;
  333. }
  334. static final ByteWindow get(PackFile pack, long offset)
  335. throws IOException {
  336. final WindowCache c = cache;
  337. final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
  338. if (c != cache) {
  339. // The cache was reconfigured while we were using the old one
  340. // to load this window. The window is still valid, but our
  341. // cache may think its still live. Ensure the window is removed
  342. // from the old cache so resources can be released.
  343. //
  344. c.removeAll();
  345. }
  346. return r;
  347. }
  348. static final void purge(PackFile pack) {
  349. cache.removeAll(pack);
  350. }
  351. /** ReferenceQueue to cleanup released and garbage collected windows. */
  352. private final ReferenceQueue<ByteWindow> queue;
  353. /** Number of entries in {@link #table}. */
  354. private final int tableSize;
  355. /** Access clock for loose LRU. */
  356. private final AtomicLong clock;
  357. /** Hash bucket directory; entries are chained below. */
  358. private final AtomicReferenceArray<Entry> table;
  359. /** Locks to prevent concurrent loads for same (PackFile,position). */
  360. private final Lock[] locks;
  361. /** Lock to elect the eviction thread after a load occurs. */
  362. private final ReentrantLock evictLock;
  363. /** Number of {@link #table} buckets to scan for an eviction window. */
  364. private final int evictBatch;
  365. private final int maxFiles;
  366. private final long maxBytes;
  367. private final boolean mmap;
  368. private final int windowSizeShift;
  369. private final int windowSize;
  370. private final StatsRecorder statsRecorder;
  371. private final StatsRecorderImpl mbean;
  372. private WindowCache(WindowCacheConfig cfg) {
  373. tableSize = tableSize(cfg);
  374. final int lockCount = lockCount(cfg);
  375. if (tableSize < 1)
  376. throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
  377. if (lockCount < 1)
  378. throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
  379. queue = new ReferenceQueue<>();
  380. clock = new AtomicLong(1);
  381. table = new AtomicReferenceArray<>(tableSize);
  382. locks = new Lock[lockCount];
  383. for (int i = 0; i < locks.length; i++)
  384. locks[i] = new Lock();
  385. evictLock = new ReentrantLock();
  386. int eb = (int) (tableSize * .1);
  387. if (64 < eb)
  388. eb = 64;
  389. else if (eb < 4)
  390. eb = 4;
  391. if (tableSize < eb)
  392. eb = tableSize;
  393. evictBatch = eb;
  394. maxFiles = cfg.getPackedGitOpenFiles();
  395. maxBytes = cfg.getPackedGitLimit();
  396. mmap = cfg.isPackedGitMMAP();
  397. windowSizeShift = bits(cfg.getPackedGitWindowSize());
  398. windowSize = 1 << windowSizeShift;
  399. mbean = new StatsRecorderImpl();
  400. statsRecorder = mbean;
  401. Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$
  402. if (maxFiles < 1)
  403. throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
  404. if (maxBytes < windowSize)
  405. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  406. }
  407. /**
  408. * @return cache statistics for the WindowCache
  409. */
  410. public WindowCacheStats getStats() {
  411. return statsRecorder.getStats();
  412. }
  413. /**
  414. * Reset stats. Does not reset open bytes and open files stats.
  415. */
  416. public void resetStats() {
  417. mbean.resetCounters();
  418. }
  419. private int hash(int packHash, long off) {
  420. return packHash + (int) (off >>> windowSizeShift);
  421. }
  422. private ByteWindow load(PackFile pack, long offset) throws IOException {
  423. long startTime = System.nanoTime();
  424. if (pack.beginWindowCache())
  425. statsRecorder.recordOpenFiles(1);
  426. try {
  427. if (mmap)
  428. return pack.mmap(offset, windowSize);
  429. ByteArrayWindow w = pack.read(offset, windowSize);
  430. statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
  431. return w;
  432. } catch (IOException | RuntimeException | Error e) {
  433. close(pack);
  434. statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
  435. throw e;
  436. } finally {
  437. statsRecorder.recordMisses(1);
  438. }
  439. }
  440. private Ref createRef(PackFile p, long o, ByteWindow v) {
  441. final Ref ref = new Ref(p, o, v, queue);
  442. statsRecorder.recordOpenBytes(ref.size);
  443. return ref;
  444. }
  445. private void clear(Ref ref) {
  446. statsRecorder.recordOpenBytes(-ref.size);
  447. statsRecorder.recordEvictions(1);
  448. close(ref.pack);
  449. }
  450. private void close(PackFile pack) {
  451. if (pack.endWindowCache()) {
  452. statsRecorder.recordOpenFiles(-1);
  453. }
  454. }
  455. private boolean isFull() {
  456. return maxFiles < mbean.getOpenFileCount()
  457. || maxBytes < mbean.getOpenByteCount();
  458. }
  459. private long toStart(long offset) {
  460. return (offset >>> windowSizeShift) << windowSizeShift;
  461. }
  462. private static int tableSize(WindowCacheConfig cfg) {
  463. final int wsz = cfg.getPackedGitWindowSize();
  464. final long limit = cfg.getPackedGitLimit();
  465. if (wsz <= 0)
  466. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  467. if (limit < wsz)
  468. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  469. return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
  470. }
  471. private static int lockCount(WindowCacheConfig cfg) {
  472. return Math.max(cfg.getPackedGitOpenFiles(), 32);
  473. }
  474. /**
  475. * Lookup a cached object, creating and loading it if it doesn't exist.
  476. *
  477. * @param pack
  478. * the pack that "contains" the cached object.
  479. * @param position
  480. * offset within <code>pack</code> of the object.
  481. * @return the object reference.
  482. * @throws IOException
  483. * the object reference was not in the cache and could not be
  484. * obtained by {@link #load(PackFile, long)}.
  485. */
  486. private ByteWindow getOrLoad(PackFile pack, long position)
  487. throws IOException {
  488. final int slot = slot(pack, position);
  489. final Entry e1 = table.get(slot);
  490. ByteWindow v = scan(e1, pack, position);
  491. if (v != null) {
  492. statsRecorder.recordHits(1);
  493. return v;
  494. }
  495. synchronized (lock(pack, position)) {
  496. Entry e2 = table.get(slot);
  497. if (e2 != e1) {
  498. v = scan(e2, pack, position);
  499. if (v != null) {
  500. statsRecorder.recordHits(1);
  501. return v;
  502. }
  503. }
  504. v = load(pack, position);
  505. final Ref ref = createRef(pack, position, v);
  506. hit(ref);
  507. for (;;) {
  508. final Entry n = new Entry(clean(e2), ref);
  509. if (table.compareAndSet(slot, e2, n))
  510. break;
  511. e2 = table.get(slot);
  512. }
  513. }
  514. if (evictLock.tryLock()) {
  515. try {
  516. gc();
  517. evict();
  518. } finally {
  519. evictLock.unlock();
  520. }
  521. }
  522. return v;
  523. }
  524. private ByteWindow scan(Entry n, PackFile pack, long position) {
  525. for (; n != null; n = n.next) {
  526. final Ref r = n.ref;
  527. if (r.pack == pack && r.position == position) {
  528. final ByteWindow v = r.get();
  529. if (v != null) {
  530. hit(r);
  531. return v;
  532. }
  533. n.kill();
  534. break;
  535. }
  536. }
  537. return null;
  538. }
  539. private void hit(Ref r) {
  540. // We don't need to be 100% accurate here. Its sufficient that at least
  541. // one thread performs the increment. Any other concurrent access at
  542. // exactly the same time can simply use the same clock value.
  543. //
  544. // Consequently we attempt the set, but we don't try to recover should
  545. // it fail. This is why we don't use getAndIncrement() here.
  546. //
  547. final long c = clock.get();
  548. clock.compareAndSet(c, c + 1);
  549. r.lastAccess = c;
  550. }
  551. private void evict() {
  552. while (isFull()) {
  553. int ptr = rng.nextInt(tableSize);
  554. Entry old = null;
  555. int slot = 0;
  556. for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
  557. if (tableSize <= ptr)
  558. ptr = 0;
  559. for (Entry e = table.get(ptr); e != null; e = e.next) {
  560. if (e.dead)
  561. continue;
  562. if (old == null || e.ref.lastAccess < old.ref.lastAccess) {
  563. old = e;
  564. slot = ptr;
  565. }
  566. }
  567. }
  568. if (old != null) {
  569. old.kill();
  570. gc();
  571. final Entry e1 = table.get(slot);
  572. table.compareAndSet(slot, e1, clean(e1));
  573. }
  574. }
  575. }
  576. /**
  577. * Clear every entry from the cache.
  578. * <p>
  579. * This is a last-ditch effort to clear out the cache, such as before it
  580. * gets replaced by another cache that is configured differently. This
  581. * method tries to force every cached entry through {@link #clear(Ref)} to
  582. * ensure that resources are correctly accounted for and cleaned up by the
  583. * subclass. A concurrent reader loading entries while this method is
  584. * running may cause resource accounting failures.
  585. */
  586. private void removeAll() {
  587. for (int s = 0; s < tableSize; s++) {
  588. Entry e1;
  589. do {
  590. e1 = table.get(s);
  591. for (Entry e = e1; e != null; e = e.next)
  592. e.kill();
  593. } while (!table.compareAndSet(s, e1, null));
  594. }
  595. gc();
  596. }
  597. /**
  598. * Clear all entries related to a single file.
  599. * <p>
  600. * Typically this method is invoked during {@link PackFile#close()}, when we
  601. * know the pack is never going to be useful to us again (for example, it no
  602. * longer exists on disk). A concurrent reader loading an entry from this
  603. * same pack may cause the pack to become stuck in the cache anyway.
  604. *
  605. * @param pack
  606. * the file to purge all entries of.
  607. */
  608. private void removeAll(PackFile pack) {
  609. for (int s = 0; s < tableSize; s++) {
  610. final Entry e1 = table.get(s);
  611. boolean hasDead = false;
  612. for (Entry e = e1; e != null; e = e.next) {
  613. if (e.ref.pack == pack) {
  614. e.kill();
  615. hasDead = true;
  616. } else if (e.dead)
  617. hasDead = true;
  618. }
  619. if (hasDead)
  620. table.compareAndSet(s, e1, clean(e1));
  621. }
  622. gc();
  623. }
  624. private void gc() {
  625. Ref r;
  626. while ((r = (Ref) queue.poll()) != null) {
  627. clear(r);
  628. final int s = slot(r.pack, r.position);
  629. final Entry e1 = table.get(s);
  630. for (Entry n = e1; n != null; n = n.next) {
  631. if (n.ref == r) {
  632. n.dead = true;
  633. table.compareAndSet(s, e1, clean(e1));
  634. break;
  635. }
  636. }
  637. }
  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.enqueue();
  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 Ref 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, Ref r) {
  669. next = n;
  670. ref = r;
  671. }
  672. final void kill() {
  673. dead = true;
  674. ref.enqueue();
  675. }
  676. }
  677. /** A soft reference wrapped around a cached object. */
  678. private static class Ref extends SoftReference<ByteWindow> {
  679. final PackFile pack;
  680. final long position;
  681. final int size;
  682. long lastAccess;
  683. protected Ref(final PackFile pack, final long position,
  684. final ByteWindow v, final ReferenceQueue<ByteWindow> queue) {
  685. super(v, queue);
  686. this.pack = pack;
  687. this.position = position;
  688. this.size = v.size();
  689. }
  690. }
  691. private static final class Lock {
  692. // Used only for its implicit monitor.
  693. }
  694. }