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

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