2 * Copyright (C) 2008, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
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
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
;
45 * The WindowCache manages reusable <code>Windows</code> and inflaters used by
46 * the other windowed file access classes.
48 public class WindowCache
{
49 private static final int bits(int newSize
) {
51 throw new IllegalArgumentException("Invalid window size");
52 if (Integer
.bitCount(newSize
) != 1)
53 throw new IllegalArgumentException("Window size must be power of 2");
54 return Integer
.numberOfTrailingZeros(newSize
);
57 private static int maxFileCount
;
59 private static int maxByteCount
;
61 private static int windowSize
;
63 private static int windowSizeShift
;
67 static final ReferenceQueue
<?
> clearedWindowQueue
;
69 private static ByteWindow
[] cache
;
71 private static ByteWindow lruHead
;
73 private static ByteWindow lruTail
;
75 private static int openFileCount
;
77 private static int openByteCount
;
80 final WindowCacheConfig c
= new WindowCacheConfig();
81 maxFileCount
= c
.getPackedGitOpenFiles();
82 maxByteCount
= c
.getPackedGitLimit();
83 windowSizeShift
= bits(c
.getPackedGitWindowSize());
84 windowSize
= 1 << windowSizeShift
;
85 mmap
= c
.isPackedGitMMAP();
86 cache
= new ByteWindow
[cacheTableSize()];
87 clearedWindowQueue
= new ReferenceQueue
<Object
>();
90 private static int cacheTableSize() {
91 return 5 * (maxByteCount
/ windowSize
) / 2;
95 * Modify the configuration of the window cache.
97 * The new configuration is applied immediately. If the new limits are
98 * smaller than what what is currently cached, older entries will be purged
99 * as soon as possible to allow the cache to meet the new limit.
101 * @param packedGitLimit
102 * maximum number of bytes to hold within this instance.
103 * @param packedGitWindowSize
104 * number of bytes per window within the cache.
105 * @param packedGitMMAP
106 * true to enable use of mmap when creating windows.
107 * @param deltaBaseCacheLimit
108 * number of bytes to hold in the delta base cache.
109 * @deprecated Use {@link WindowCacheConfig} instead.
111 public static void reconfigure(final int packedGitLimit
,
112 final int packedGitWindowSize
, final boolean packedGitMMAP
,
113 final int deltaBaseCacheLimit
) {
114 final WindowCacheConfig c
= new WindowCacheConfig();
115 c
.setPackedGitLimit(packedGitLimit
);
116 c
.setPackedGitWindowSize(packedGitWindowSize
);
117 c
.setPackedGitMMAP(packedGitMMAP
);
118 c
.setDeltaBaseCacheLimit(deltaBaseCacheLimit
);
123 * Modify the configuration of the window cache.
125 * The new configuration is applied immediately. If the new limits are
126 * smaller than what what is currently cached, older entries will be purged
127 * as soon as possible to allow the cache to meet the new limit.
130 * the new window cache configuration.
132 public static void reconfigure(final WindowCacheConfig cfg
) {
133 reconfigureImpl(cfg
);
134 UnpackedObjectCache
.reconfigure(cfg
);
137 private static synchronized void reconfigureImpl(final WindowCacheConfig cfg
) {
138 boolean prune
= false;
139 boolean evictAll
= false;
141 if (maxFileCount
< cfg
.getPackedGitOpenFiles())
142 maxFileCount
= cfg
.getPackedGitOpenFiles();
143 else if (maxFileCount
> cfg
.getPackedGitOpenFiles()) {
144 maxFileCount
= cfg
.getPackedGitOpenFiles();
148 if (maxByteCount
< cfg
.getPackedGitLimit()) {
149 maxByteCount
= cfg
.getPackedGitLimit();
150 } else if (maxByteCount
> cfg
.getPackedGitLimit()) {
151 maxByteCount
= cfg
.getPackedGitLimit();
155 if (bits(cfg
.getPackedGitWindowSize()) != windowSizeShift
) {
156 windowSizeShift
= bits(cfg
.getPackedGitWindowSize());
157 windowSize
= 1 << windowSizeShift
;
161 if (mmap
!= cfg
.isPackedGitMMAP()) {
162 mmap
= cfg
.isPackedGitMMAP();
167 // We have to throw away every window we have. None
168 // of them are suitable for the new configuration.
170 for (ByteWindow
<?
> e
: cache
) {
171 for (; e
!= null; e
= e
.chainNext
)
174 runClearedWindowQueue();
175 cache
= new ByteWindow
[cacheTableSize()];
179 // We should decrease our memory usage.
182 runClearedWindowQueue();
185 if (cache
.length
!= cacheTableSize()) {
186 // The cache table should be resized.
187 // Rehash every entry.
189 final ByteWindow
[] priorTable
= cache
;
191 cache
= new ByteWindow
[cacheTableSize()];
192 for (ByteWindow
<?
> e
: priorTable
) {
193 for (ByteWindow
<?
> n
; e
!= null; e
= n
) {
195 final int idx
= hash(e
.provider
, e
.id
);
196 e
.chainNext
= cache
[idx
];
205 * Get a specific window.
208 * an active cursor object to maintain the window reference while
209 * the caller needs it.
211 * the provider of the window. If the window is not currently in
212 * the cache then the provider will be asked to load it.
214 * offset (in bytes) within the file that the caller needs access
216 * @throws IOException
217 * the window was not found in the cache and the given provider
218 * was unable to load the window on demand.
220 public static final void get(final WindowCursor curs
, final PackFile wp
,
221 final long position
) throws IOException
{
222 getImpl(curs
, wp
, position
);
223 curs
.window
.ensureLoaded(curs
.handle
);
226 static synchronized final void pin(final PackFile wp
) throws IOException
{
227 if (++wp
.openCount
== 1) {
232 static synchronized final void unpin(final PackFile wp
) {
233 if (--wp
.openCount
== 0) {
239 private static synchronized final void getImpl(final WindowCursor curs
,
240 final PackFile wp
, final long position
) throws IOException
{
241 final int id
= (int) (position
>> windowSizeShift
);
242 final int idx
= hash(wp
, id
);
243 for (ByteWindow
<?
> e
= cache
[idx
]; e
!= null; e
= e
.chainNext
) {
244 if (e
.provider
== wp
&& e
.id
== id
) {
245 if ((curs
.handle
= e
.get()) != null) {
256 if (wp
.openCount
== 0) {
259 // The cacheOpen may have mapped the window we are trying to
260 // map ourselves. Retrying the search ensures that does not
263 for (ByteWindow
<?
> e
= cache
[idx
]; e
!= null; e
= e
.chainNext
) {
264 if (e
.provider
== wp
&& e
.id
== id
) {
265 if ((curs
.handle
= e
.get()) != null) {
277 final int wsz
= windowSize(wp
, id
);
279 openByteCount
+= wsz
;
281 runClearedWindowQueue();
283 wp
.allocWindow(curs
, id
, (position
>>> windowSizeShift
) << windowSizeShift
, wsz
);
284 final ByteWindow
<?
> e
= curs
.window
;
285 e
.chainNext
= cache
[idx
];
290 private static void openFile(final PackFile wp
) throws IOException
{
294 runClearedWindowQueue();
297 } catch (IOException ioe
) {
301 } catch (RuntimeException ioe
) {
305 } catch (Error ioe
) {
314 static synchronized void markLoaded(final ByteWindow w
) {
315 if (--w
.provider
.openCount
== 0) {
317 w
.provider
.cacheClose();
321 private static void makeMostRecent(ByteWindow
<?
> e
) {
328 private static void releaseMemory() {
329 ByteWindow
<?
> e
= lruTail
;
330 while (isOverLimit() && e
!= null) {
331 final ByteWindow
<?
> p
= e
.lruPrev
;
337 private static boolean isOverLimit() {
338 return openByteCount
> maxByteCount
|| openFileCount
> maxFileCount
;
342 * Remove all windows associated with a specific provider.
344 * Providers should invoke this method as part of their cleanup/close
345 * routines, ensuring that the window cache releases all windows that cannot
346 * ever be requested again.
350 * the window provider whose windows should be removed from the
353 public static synchronized final void purge(final PackFile wp
) {
354 for (ByteWindow e
: cache
) {
355 for (; e
!= null; e
= e
.chainNext
) {
356 if (e
.provider
== wp
)
360 runClearedWindowQueue();
363 private static void runClearedWindowQueue() {
365 while ((e
= (ByteWindow
) clearedWindowQueue
.poll()) != null) {
375 private static void clear(final ByteWindow
<?
> e
) {
381 private static void unlinkSize(final ByteWindow
<?
> e
) {
383 if (--e
.provider
.openCount
== 0) {
385 e
.provider
.cacheClose();
387 openByteCount
-= e
.size
;
388 e
.sizeActive
= false;
392 private static void unlinkCache(final ByteWindow dead
) {
393 final int idx
= hash(dead
.provider
, dead
.id
);
394 ByteWindow
<?
> e
= cache
[idx
], p
= null, n
;
395 for (; e
!= null; p
= e
, e
= n
) {
407 private static void unlinkLRU(final ByteWindow e
) {
408 final ByteWindow
<?
> prev
= e
.lruPrev
;
409 final ByteWindow
<?
> next
= e
.lruNext
;
422 private static void insertLRU(final ByteWindow
<?
> e
) {
423 final ByteWindow h
= lruHead
;
433 private static int hash(final PackFile wp
, final int id
) {
434 // wp.hash was already "stirred up" a bit by * 31 when
435 // it was created. Its reasonable to just add here.
437 return ((wp
.hash
+ id
) >>> 1) % cache
.length
;
440 private static int windowSize(final PackFile file
, final int id
) {
441 final long len
= file
.length
;
442 final long pos
= id
<< windowSizeShift
;
443 return len
< pos
+ windowSize ?
(int) (len
- pos
) : windowSize
;
446 private WindowCache() {
447 throw new UnsupportedOperationException();