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

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