Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

WindowCache.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  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.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.AtomicInteger;
  50. import java.util.concurrent.atomic.AtomicLong;
  51. import java.util.concurrent.atomic.AtomicReferenceArray;
  52. import java.util.concurrent.locks.ReentrantLock;
  53. import org.eclipse.jgit.JGitText;
  54. /**
  55. * Caches slices of a {@link PackFile} in memory for faster read access.
  56. * <p>
  57. * The WindowCache serves as a Java based "buffer cache", loading segments of a
  58. * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
  59. * only tiny slices of a file, the WindowCache tries to smooth out these tiny
  60. * reads into larger block-sized IO operations.
  61. * <p>
  62. * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by
  63. * exactly one thread for the given <code>(PackFile,position)</code> key tuple.
  64. * This is ensured by an array of locks, with the tuple hashed to a lock
  65. * instance.
  66. * <p>
  67. * During a miss, older entries are evicted from the cache so long as
  68. * {@link #isFull()} returns true.
  69. * <p>
  70. * Its too expensive during object access to be 100% accurate with a least
  71. * recently used (LRU) algorithm. Strictly ordering every read is a lot of
  72. * overhead that typically doesn't yield a corresponding benefit to the
  73. * application.
  74. * <p>
  75. * This cache implements a loose LRU policy by randomly picking a window
  76. * comprised of roughly 10% of the cache, and evicting the oldest accessed entry
  77. * within that window.
  78. * <p>
  79. * Entities created by the cache are held under SoftReferences, permitting the
  80. * Java runtime's garbage collector to evict entries when heap memory gets low.
  81. * Most JREs implement a loose least recently used algorithm for this eviction.
  82. * <p>
  83. * The internal hash table does not expand at runtime, instead it is fixed in
  84. * size at cache creation time. The internal lock table used to gate load
  85. * invocations is also fixed in size.
  86. * <p>
  87. * The key tuple is passed through to methods as a pair of parameters rather
  88. * than as a single Object, thus reducing the transient memory allocations of
  89. * callers. It is more efficient to avoid the allocation, as we can't be 100%
  90. * sure that a JIT would be able to stack-allocate a key tuple.
  91. * <p>
  92. * This cache has an implementation rule such that:
  93. * <ul>
  94. * <li>{@link #load(PackFile, long)} is invoked by at most one thread at a time
  95. * for a given <code>(PackFile,position)</code> tuple.</li>
  96. * <li>For every <code>load()</code> invocation there is exactly one
  97. * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a
  98. * SoftReference around the cached entity.</li>
  99. * <li>For every Reference created by <code>createRef()</code> there will be
  100. * exactly one call to {@link #clear(Ref)} to cleanup any resources associated
  101. * with the (now expired) cached entity.</li>
  102. * </ul>
  103. * <p>
  104. * Therefore, it is safe to perform resource accounting increments during the
  105. * {@link #load(PackFile, long)} or
  106. * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching
  107. * decrements during {@link #clear(Ref)}. Implementors may need to override
  108. * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional
  109. * accounting information into an implementation specific {@link Ref} subclass,
  110. * as the cached entity may have already been evicted by the JRE's garbage
  111. * collector.
  112. * <p>
  113. * To maintain higher concurrency workloads, during eviction only one thread
  114. * performs the eviction work, while other threads can continue to insert new
  115. * objects in parallel. This means that the cache can be temporarily over limit,
  116. * especially if the nominated eviction thread is being starved relative to the
  117. * other threads.
  118. */
  119. public class WindowCache {
  120. private static final int bits(int newSize) {
  121. if (newSize < 4096)
  122. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  123. if (Integer.bitCount(newSize) != 1)
  124. throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
  125. return Integer.numberOfTrailingZeros(newSize);
  126. }
  127. private static final Random rng = new Random();
  128. private static volatile WindowCache cache;
  129. static {
  130. reconfigure(new WindowCacheConfig());
  131. }
  132. /**
  133. * Modify the configuration of the window cache.
  134. * <p>
  135. * The new configuration is applied immediately. If the new limits are
  136. * smaller than what what is currently cached, older entries will be purged
  137. * as soon as possible to allow the cache to meet the new limit.
  138. *
  139. * @param packedGitLimit
  140. * maximum number of bytes to hold within this instance.
  141. * @param packedGitWindowSize
  142. * number of bytes per window within the cache.
  143. * @param packedGitMMAP
  144. * true to enable use of mmap when creating windows.
  145. * @param deltaBaseCacheLimit
  146. * number of bytes to hold in the delta base cache.
  147. * @deprecated Use {@link WindowCacheConfig} instead.
  148. */
  149. public static void reconfigure(final int packedGitLimit,
  150. final int packedGitWindowSize, final boolean packedGitMMAP,
  151. final int deltaBaseCacheLimit) {
  152. final WindowCacheConfig c = new WindowCacheConfig();
  153. c.setPackedGitLimit(packedGitLimit);
  154. c.setPackedGitWindowSize(packedGitWindowSize);
  155. c.setPackedGitMMAP(packedGitMMAP);
  156. c.setDeltaBaseCacheLimit(deltaBaseCacheLimit);
  157. reconfigure(c);
  158. }
  159. /**
  160. * Modify the configuration of the window cache.
  161. * <p>
  162. * The new configuration is applied immediately. If the new limits are
  163. * smaller than what what is currently cached, older entries will be purged
  164. * as soon as possible to allow the cache to meet the new limit.
  165. *
  166. * @param cfg
  167. * the new window cache configuration.
  168. * @throws IllegalArgumentException
  169. * the cache configuration contains one or more invalid
  170. * settings, usually too low of a limit.
  171. */
  172. public static void reconfigure(final WindowCacheConfig cfg) {
  173. final WindowCache nc = new WindowCache(cfg);
  174. final WindowCache oc = cache;
  175. if (oc != null)
  176. oc.removeAll();
  177. cache = nc;
  178. UnpackedObjectCache.reconfigure(cfg);
  179. }
  180. static WindowCache getInstance() {
  181. return cache;
  182. }
  183. static final ByteWindow get(final PackFile pack, final long offset)
  184. throws IOException {
  185. final WindowCache c = cache;
  186. final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
  187. if (c != cache) {
  188. // The cache was reconfigured while we were using the old one
  189. // to load this window. The window is still valid, but our
  190. // cache may think its still live. Ensure the window is removed
  191. // from the old cache so resources can be released.
  192. //
  193. c.removeAll();
  194. }
  195. return r;
  196. }
  197. static final void purge(final PackFile pack) {
  198. cache.removeAll(pack);
  199. }
  200. /** ReferenceQueue to cleanup released and garbage collected windows. */
  201. private final ReferenceQueue<ByteWindow> queue;
  202. /** Number of entries in {@link #table}. */
  203. private final int tableSize;
  204. /** Access clock for loose LRU. */
  205. private final AtomicLong clock;
  206. /** Hash bucket directory; entries are chained below. */
  207. private final AtomicReferenceArray<Entry> table;
  208. /** Locks to prevent concurrent loads for same (PackFile,position). */
  209. private final Lock[] locks;
  210. /** Lock to elect the eviction thread after a load occurs. */
  211. private final ReentrantLock evictLock;
  212. /** Number of {@link #table} buckets to scan for an eviction window. */
  213. private final int evictBatch;
  214. private final int maxFiles;
  215. private final long maxBytes;
  216. private final boolean mmap;
  217. private final int windowSizeShift;
  218. private final int windowSize;
  219. private final AtomicInteger openFiles;
  220. private final AtomicLong openBytes;
  221. private WindowCache(final WindowCacheConfig cfg) {
  222. tableSize = tableSize(cfg);
  223. final int lockCount = lockCount(cfg);
  224. if (tableSize < 1)
  225. throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
  226. if (lockCount < 1)
  227. throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
  228. queue = new ReferenceQueue<ByteWindow>();
  229. clock = new AtomicLong(1);
  230. table = new AtomicReferenceArray<Entry>(tableSize);
  231. locks = new Lock[lockCount];
  232. for (int i = 0; i < locks.length; i++)
  233. locks[i] = new Lock();
  234. evictLock = new ReentrantLock();
  235. int eb = (int) (tableSize * .1);
  236. if (64 < eb)
  237. eb = 64;
  238. else if (eb < 4)
  239. eb = 4;
  240. if (tableSize < eb)
  241. eb = tableSize;
  242. evictBatch = eb;
  243. maxFiles = cfg.getPackedGitOpenFiles();
  244. maxBytes = cfg.getPackedGitLimit();
  245. mmap = cfg.isPackedGitMMAP();
  246. windowSizeShift = bits(cfg.getPackedGitWindowSize());
  247. windowSize = 1 << windowSizeShift;
  248. openFiles = new AtomicInteger();
  249. openBytes = new AtomicLong();
  250. if (maxFiles < 1)
  251. throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
  252. if (maxBytes < windowSize)
  253. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  254. }
  255. int getOpenFiles() {
  256. return openFiles.get();
  257. }
  258. long getOpenBytes() {
  259. return openBytes.get();
  260. }
  261. private int hash(final int packHash, final long off) {
  262. return packHash + (int) (off >>> windowSizeShift);
  263. }
  264. private ByteWindow load(final PackFile pack, final long offset)
  265. throws IOException {
  266. if (pack.beginWindowCache())
  267. openFiles.incrementAndGet();
  268. try {
  269. if (mmap)
  270. return pack.mmap(offset, windowSize);
  271. return pack.read(offset, windowSize);
  272. } catch (IOException e) {
  273. close(pack);
  274. throw e;
  275. } catch (RuntimeException e) {
  276. close(pack);
  277. throw e;
  278. } catch (Error e) {
  279. close(pack);
  280. throw e;
  281. }
  282. }
  283. private Ref createRef(final PackFile p, final long o, final ByteWindow v) {
  284. final Ref ref = new Ref(p, o, v, queue);
  285. openBytes.addAndGet(ref.size);
  286. return ref;
  287. }
  288. private void clear(final Ref ref) {
  289. openBytes.addAndGet(-ref.size);
  290. close(ref.pack);
  291. }
  292. private void close(final PackFile pack) {
  293. if (pack.endWindowCache())
  294. openFiles.decrementAndGet();
  295. }
  296. private boolean isFull() {
  297. return maxFiles < openFiles.get() || maxBytes < openBytes.get();
  298. }
  299. private long toStart(final long offset) {
  300. return (offset >>> windowSizeShift) << windowSizeShift;
  301. }
  302. private static int tableSize(final WindowCacheConfig cfg) {
  303. final int wsz = cfg.getPackedGitWindowSize();
  304. final long limit = cfg.getPackedGitLimit();
  305. if (wsz <= 0)
  306. throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
  307. if (limit < wsz)
  308. throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
  309. return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
  310. }
  311. private static int lockCount(final WindowCacheConfig cfg) {
  312. return Math.max(cfg.getPackedGitOpenFiles(), 32);
  313. }
  314. /**
  315. * Lookup a cached object, creating and loading it if it doesn't exist.
  316. *
  317. * @param pack
  318. * the pack that "contains" the cached object.
  319. * @param position
  320. * offset within <code>pack</code> of the object.
  321. * @return the object reference.
  322. * @throws IOException
  323. * the object reference was not in the cache and could not be
  324. * obtained by {@link #load(PackFile, long)}.
  325. */
  326. private ByteWindow getOrLoad(final PackFile pack, final long position)
  327. throws IOException {
  328. final int slot = slot(pack, position);
  329. final Entry e1 = table.get(slot);
  330. ByteWindow v = scan(e1, pack, position);
  331. if (v != null)
  332. return v;
  333. synchronized (lock(pack, position)) {
  334. Entry e2 = table.get(slot);
  335. if (e2 != e1) {
  336. v = scan(e2, pack, position);
  337. if (v != null)
  338. return v;
  339. }
  340. v = load(pack, position);
  341. final Ref ref = createRef(pack, position, v);
  342. hit(ref);
  343. for (;;) {
  344. final Entry n = new Entry(clean(e2), ref);
  345. if (table.compareAndSet(slot, e2, n))
  346. break;
  347. e2 = table.get(slot);
  348. }
  349. }
  350. if (evictLock.tryLock()) {
  351. try {
  352. gc();
  353. evict();
  354. } finally {
  355. evictLock.unlock();
  356. }
  357. }
  358. return v;
  359. }
  360. private ByteWindow scan(Entry n, final PackFile pack, final long position) {
  361. for (; n != null; n = n.next) {
  362. final Ref r = n.ref;
  363. if (r.pack == pack && r.position == position) {
  364. final ByteWindow v = r.get();
  365. if (v != null) {
  366. hit(r);
  367. return v;
  368. }
  369. n.kill();
  370. break;
  371. }
  372. }
  373. return null;
  374. }
  375. private void hit(final Ref r) {
  376. // We don't need to be 100% accurate here. Its sufficient that at least
  377. // one thread performs the increment. Any other concurrent access at
  378. // exactly the same time can simply use the same clock value.
  379. //
  380. // Consequently we attempt the set, but we don't try to recover should
  381. // it fail. This is why we don't use getAndIncrement() here.
  382. //
  383. final long c = clock.get();
  384. clock.compareAndSet(c, c + 1);
  385. r.lastAccess = c;
  386. }
  387. private void evict() {
  388. while (isFull()) {
  389. int ptr = rng.nextInt(tableSize);
  390. Entry old = null;
  391. int slot = 0;
  392. for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
  393. if (tableSize <= ptr)
  394. ptr = 0;
  395. for (Entry e = table.get(ptr); e != null; e = e.next) {
  396. if (e.dead)
  397. continue;
  398. if (old == null || e.ref.lastAccess < old.ref.lastAccess) {
  399. old = e;
  400. slot = ptr;
  401. }
  402. }
  403. }
  404. if (old != null) {
  405. old.kill();
  406. gc();
  407. final Entry e1 = table.get(slot);
  408. table.compareAndSet(slot, e1, clean(e1));
  409. }
  410. }
  411. }
  412. /**
  413. * Clear every entry from the cache.
  414. * <p>
  415. * This is a last-ditch effort to clear out the cache, such as before it
  416. * gets replaced by another cache that is configured differently. This
  417. * method tries to force every cached entry through {@link #clear(Ref)} to
  418. * ensure that resources are correctly accounted for and cleaned up by the
  419. * subclass. A concurrent reader loading entries while this method is
  420. * running may cause resource accounting failures.
  421. */
  422. private void removeAll() {
  423. for (int s = 0; s < tableSize; s++) {
  424. Entry e1;
  425. do {
  426. e1 = table.get(s);
  427. for (Entry e = e1; e != null; e = e.next)
  428. e.kill();
  429. } while (!table.compareAndSet(s, e1, null));
  430. }
  431. gc();
  432. }
  433. /**
  434. * Clear all entries related to a single file.
  435. * <p>
  436. * Typically this method is invoked during {@link PackFile#close()}, when we
  437. * know the pack is never going to be useful to us again (for example, it no
  438. * longer exists on disk). A concurrent reader loading an entry from this
  439. * same pack may cause the pack to become stuck in the cache anyway.
  440. *
  441. * @param pack
  442. * the file to purge all entries of.
  443. */
  444. private void removeAll(final PackFile pack) {
  445. for (int s = 0; s < tableSize; s++) {
  446. final Entry e1 = table.get(s);
  447. boolean hasDead = false;
  448. for (Entry e = e1; e != null; e = e.next) {
  449. if (e.ref.pack == pack) {
  450. e.kill();
  451. hasDead = true;
  452. } else if (e.dead)
  453. hasDead = true;
  454. }
  455. if (hasDead)
  456. table.compareAndSet(s, e1, clean(e1));
  457. }
  458. gc();
  459. }
  460. @SuppressWarnings("unchecked")
  461. private void gc() {
  462. Ref r;
  463. while ((r = (Ref) queue.poll()) != null) {
  464. // Sun's Java 5 and 6 implementation have a bug where a Reference
  465. // can be enqueued and dequeued twice on the same reference queue
  466. // due to a race condition within ReferenceQueue.enqueue(Reference).
  467. //
  468. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858
  469. //
  470. // We CANNOT permit a Reference to come through us twice, as it will
  471. // skew the resource counters we maintain. Our canClear() check here
  472. // provides a way to skip the redundant dequeues, if any.
  473. //
  474. if (r.canClear()) {
  475. clear(r);
  476. boolean found = false;
  477. final int s = slot(r.pack, r.position);
  478. final Entry e1 = table.get(s);
  479. for (Entry n = e1; n != null; n = n.next) {
  480. if (n.ref == r) {
  481. n.dead = true;
  482. found = true;
  483. break;
  484. }
  485. }
  486. if (found)
  487. table.compareAndSet(s, e1, clean(e1));
  488. }
  489. }
  490. }
  491. private int slot(final PackFile pack, final long position) {
  492. return (hash(pack.hash, position) >>> 1) % tableSize;
  493. }
  494. private Lock lock(final PackFile pack, final long position) {
  495. return locks[(hash(pack.hash, position) >>> 1) % locks.length];
  496. }
  497. private static Entry clean(Entry top) {
  498. while (top != null && top.dead) {
  499. top.ref.enqueue();
  500. top = top.next;
  501. }
  502. if (top == null)
  503. return null;
  504. final Entry n = clean(top.next);
  505. return n == top.next ? top : new Entry(n, top.ref);
  506. }
  507. private static class Entry {
  508. /** Next entry in the hash table's chain list. */
  509. final Entry next;
  510. /** The referenced object. */
  511. final Ref ref;
  512. /**
  513. * Marked true when ref.get() returns null and the ref is dead.
  514. * <p>
  515. * A true here indicates that the ref is no longer accessible, and that
  516. * we therefore need to eventually purge this Entry object out of the
  517. * bucket's chain.
  518. */
  519. volatile boolean dead;
  520. Entry(final Entry n, final Ref r) {
  521. next = n;
  522. ref = r;
  523. }
  524. final void kill() {
  525. dead = true;
  526. ref.enqueue();
  527. }
  528. }
  529. /** A soft reference wrapped around a cached object. */
  530. private static class Ref extends SoftReference<ByteWindow> {
  531. final PackFile pack;
  532. final long position;
  533. final int size;
  534. long lastAccess;
  535. private boolean cleared;
  536. protected Ref(final PackFile pack, final long position,
  537. final ByteWindow v, final ReferenceQueue<ByteWindow> queue) {
  538. super(v, queue);
  539. this.pack = pack;
  540. this.position = position;
  541. this.size = v.size();
  542. }
  543. final synchronized boolean canClear() {
  544. if (cleared)
  545. return false;
  546. cleared = true;
  547. return true;
  548. }
  549. }
  550. private static final class Lock {
  551. // Used only for its implicit monitor.
  552. }
  553. }