Add tests to increase coverage of WindowCache
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / WindowCache.java
blob0c60853147d93618ff64511c6d203f461f7de0cb
1 /*
2 * Copyright (C) 2008, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.lib;
41 import java.io.IOException;
42 import java.lang.ref.ReferenceQueue;
43 import java.util.concurrent.atomic.AtomicInteger;
45 /**
46 * Caches slices of a {@link PackFile} in memory for faster read access.
47 * <p>
48 * The WindowCache serves as a Java based "buffer cache", loading segments of a
49 * PackFile into the JVM heap prior to use. As JGit often wants to do reads of
50 * only tiny slices of a file, the WindowCache tries to smooth out these tiny
51 * reads into larger block-sized IO operations.
53 public class WindowCache extends OffsetCache<ByteWindow, WindowCache.WindowRef> {
54 private static final int bits(int newSize) {
55 if (newSize < 4096)
56 throw new IllegalArgumentException("Invalid window size");
57 if (Integer.bitCount(newSize) != 1)
58 throw new IllegalArgumentException("Window size must be power of 2");
59 return Integer.numberOfTrailingZeros(newSize);
62 private static volatile WindowCache cache;
64 static {
65 reconfigure(new WindowCacheConfig());
68 /**
69 * Modify the configuration of the window cache.
70 * <p>
71 * The new configuration is applied immediately. If the new limits are
72 * smaller than what what is currently cached, older entries will be purged
73 * as soon as possible to allow the cache to meet the new limit.
75 * @param packedGitLimit
76 * maximum number of bytes to hold within this instance.
77 * @param packedGitWindowSize
78 * number of bytes per window within the cache.
79 * @param packedGitMMAP
80 * true to enable use of mmap when creating windows.
81 * @param deltaBaseCacheLimit
82 * number of bytes to hold in the delta base cache.
83 * @deprecated Use {@link WindowCacheConfig} instead.
85 public static void reconfigure(final int packedGitLimit,
86 final int packedGitWindowSize, final boolean packedGitMMAP,
87 final int deltaBaseCacheLimit) {
88 final WindowCacheConfig c = new WindowCacheConfig();
89 c.setPackedGitLimit(packedGitLimit);
90 c.setPackedGitWindowSize(packedGitWindowSize);
91 c.setPackedGitMMAP(packedGitMMAP);
92 c.setDeltaBaseCacheLimit(deltaBaseCacheLimit);
93 reconfigure(c);
96 /**
97 * Modify the configuration of the window cache.
98 * <p>
99 * The new configuration is applied immediately. If the new limits are
100 * smaller than what what is currently cached, older entries will be purged
101 * as soon as possible to allow the cache to meet the new limit.
103 * @param cfg
104 * the new window cache configuration.
105 * @throws IllegalArgumentException
106 * the cache configuration contains one or more invalid
107 * settings, usually too low of a limit.
109 public static void reconfigure(final WindowCacheConfig cfg) {
110 final WindowCache nc = new WindowCache(cfg);
111 final WindowCache oc = cache;
112 if (oc != null)
113 oc.removeAll();
114 cache = nc;
115 UnpackedObjectCache.reconfigure(cfg);
118 static WindowCache getInstance() {
119 return cache;
122 static final ByteWindow get(final PackFile pack, final long offset)
123 throws IOException {
124 final WindowCache c = cache;
125 final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
126 if (c != cache) {
127 // The cache was reconfigured while we were using the old one
128 // to load this window. The window is still valid, but our
129 // cache may think its still live. Ensure the window is removed
130 // from the old cache so resources can be released.
132 c.removeAll();
134 return r;
137 static final void purge(final PackFile pack) {
138 cache.removeAll(pack);
141 private final int maxFiles;
143 private final int maxBytes;
145 private final boolean mmap;
147 private final int windowSizeShift;
149 private final int windowSize;
151 private final AtomicInteger openFiles;
153 private final AtomicInteger openBytes;
155 private WindowCache(final WindowCacheConfig cfg) {
156 super(tableSize(cfg), lockCount(cfg));
157 maxFiles = cfg.getPackedGitOpenFiles();
158 maxBytes = cfg.getPackedGitLimit();
159 mmap = cfg.isPackedGitMMAP();
160 windowSizeShift = bits(cfg.getPackedGitWindowSize());
161 windowSize = 1 << windowSizeShift;
163 openFiles = new AtomicInteger();
164 openBytes = new AtomicInteger();
166 if (maxFiles < 1)
167 throw new IllegalArgumentException("Open files must be >= 1");
168 if (maxBytes < windowSize)
169 throw new IllegalArgumentException("Window size must be < limit");
172 int getOpenFiles() {
173 return openFiles.get();
176 int getOpenBytes() {
177 return openBytes.get();
180 @Override
181 protected int hash(final int packHash, final long off) {
182 return packHash + (int) (off >>> windowSizeShift);
185 @Override
186 protected ByteWindow load(final PackFile pack, final long offset)
187 throws IOException {
188 if (pack.beginWindowCache())
189 openFiles.incrementAndGet();
190 try {
191 if (mmap)
192 return pack.mmap(offset, windowSize);
193 return pack.read(offset, windowSize);
194 } catch (IOException e) {
195 close(pack);
196 throw e;
197 } catch (RuntimeException e) {
198 close(pack);
199 throw e;
200 } catch (Error e) {
201 close(pack);
202 throw e;
206 @Override
207 protected WindowRef createRef(final PackFile p, final long o,
208 final ByteWindow v) {
209 final WindowRef ref = new WindowRef(p, o, v, queue);
210 openBytes.addAndGet(ref.size);
211 return ref;
214 @Override
215 protected void clear(final WindowRef ref) {
216 openBytes.addAndGet(-ref.size);
217 close(ref.pack);
220 private void close(final PackFile pack) {
221 if (pack.endWindowCache())
222 openFiles.decrementAndGet();
225 @Override
226 protected boolean isFull() {
227 return maxFiles < openFiles.get() || maxBytes < openBytes.get();
230 private long toStart(final long offset) {
231 return (offset >>> windowSizeShift) << windowSizeShift;
234 private static int tableSize(final WindowCacheConfig cfg) {
235 final int wsz = cfg.getPackedGitWindowSize();
236 final int limit = cfg.getPackedGitLimit();
237 if (wsz <= 0)
238 throw new IllegalArgumentException("Invalid window size");
239 if (limit < wsz)
240 throw new IllegalArgumentException("Window size must be < limit");
241 return 5 * (limit / wsz) / 2;
244 private static int lockCount(final WindowCacheConfig cfg) {
245 return Math.max(cfg.getPackedGitOpenFiles(), 32);
248 static class WindowRef extends OffsetCache.Ref<ByteWindow> {
249 final int size;
251 WindowRef(final PackFile pack, final long position, final ByteWindow v,
252 final ReferenceQueue<ByteWindow> queue) {
253 super(pack, position, v, queue);
254 size = v.size();