1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 package org.eclipse.jgit.internal.storage.file;
46
47 import java.io.IOException;
48 import java.lang.ref.ReferenceQueue;
49 import java.lang.ref.SoftReference;
50 import java.util.Random;
51 import java.util.concurrent.atomic.AtomicInteger;
52 import java.util.concurrent.atomic.AtomicLong;
53 import java.util.concurrent.atomic.AtomicReferenceArray;
54 import java.util.concurrent.locks.ReentrantLock;
55
56 import org.eclipse.jgit.internal.JGitText;
57 import org.eclipse.jgit.storage.file.WindowCacheConfig;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 public class WindowCache {
125 private static final int bits(int newSize) {
126 if (newSize < 4096)
127 throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
128 if (Integer.bitCount(newSize) != 1)
129 throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
130 return Integer.numberOfTrailingZeros(newSize);
131 }
132
133 private static final Random rng = new Random();
134
135 private static volatile WindowCache cache;
136
137 private static volatile int streamFileThreshold;
138
139 static {
140 reconfigure(new WindowCacheConfig());
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 @Deprecated
158 public static void reconfigure(final WindowCacheConfig cfg) {
159 final WindowCache nc = new WindowCache(cfg);
160 final WindowCache oc = cache;
161 if (oc != null)
162 oc.removeAll();
163 cache = nc;
164 streamFileThreshold = cfg.getStreamFileThreshold();
165 DeltaBaseCache.reconfigure(cfg);
166 }
167
168 static int getStreamFileThreshold() {
169 return streamFileThreshold;
170 }
171
172 static WindowCache getInstance() {
173 return cache;
174 }
175
176 static final ByteWindow get(final PackFile pack, final long offset)
177 throws IOException {
178 final WindowCache c = cache;
179 final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
180 if (c != cache) {
181
182
183
184
185
186 c.removeAll();
187 }
188 return r;
189 }
190
191 static final void purge(final PackFile pack) {
192 cache.removeAll(pack);
193 }
194
195
196 private final ReferenceQueue<ByteWindow> queue;
197
198
199 private final int tableSize;
200
201
202 private final AtomicLong clock;
203
204
205 private final AtomicReferenceArray<Entry> table;
206
207
208 private final Lock[] locks;
209
210
211 private final ReentrantLock evictLock;
212
213
214 private final int evictBatch;
215
216 private final int maxFiles;
217
218 private final long maxBytes;
219
220 private final boolean mmap;
221
222 private final int windowSizeShift;
223
224 private final int windowSize;
225
226 private final AtomicInteger openFiles;
227
228 private final AtomicLong openBytes;
229
230 private WindowCache(final WindowCacheConfig cfg) {
231 tableSize = tableSize(cfg);
232 final int lockCount = lockCount(cfg);
233 if (tableSize < 1)
234 throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
235 if (lockCount < 1)
236 throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
237
238 queue = new ReferenceQueue<ByteWindow>();
239 clock = new AtomicLong(1);
240 table = new AtomicReferenceArray<Entry>(tableSize);
241 locks = new Lock[lockCount];
242 for (int i = 0; i < locks.length; i++)
243 locks[i] = new Lock();
244 evictLock = new ReentrantLock();
245
246 int eb = (int) (tableSize * .1);
247 if (64 < eb)
248 eb = 64;
249 else if (eb < 4)
250 eb = 4;
251 if (tableSize < eb)
252 eb = tableSize;
253 evictBatch = eb;
254
255 maxFiles = cfg.getPackedGitOpenFiles();
256 maxBytes = cfg.getPackedGitLimit();
257 mmap = cfg.isPackedGitMMAP();
258 windowSizeShift = bits(cfg.getPackedGitWindowSize());
259 windowSize = 1 << windowSizeShift;
260
261 openFiles = new AtomicInteger();
262 openBytes = new AtomicLong();
263
264 if (maxFiles < 1)
265 throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
266 if (maxBytes < windowSize)
267 throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
268 }
269
270 int getOpenFiles() {
271 return openFiles.get();
272 }
273
274 long getOpenBytes() {
275 return openBytes.get();
276 }
277
278 private int hash(final int packHash, final long off) {
279 return packHash + (int) (off >>> windowSizeShift);
280 }
281
282 private ByteWindow load(final PackFile pack, final long offset)
283 throws IOException {
284 if (pack.beginWindowCache())
285 openFiles.incrementAndGet();
286 try {
287 if (mmap)
288 return pack.mmap(offset, windowSize);
289 return pack.read(offset, windowSize);
290 } catch (IOException e) {
291 close(pack);
292 throw e;
293 } catch (RuntimeException e) {
294 close(pack);
295 throw e;
296 } catch (Error e) {
297 close(pack);
298 throw e;
299 }
300 }
301
302 private Ref createRef(final PackFile p, final long o, final ByteWindow v) {
303 final Ref ref = new Ref(p, o, v, queue);
304 openBytes.addAndGet(ref.size);
305 return ref;
306 }
307
308 private void clear(final Ref ref) {
309 openBytes.addAndGet(-ref.size);
310 close(ref.pack);
311 }
312
313 private void close(final PackFile pack) {
314 if (pack.endWindowCache())
315 openFiles.decrementAndGet();
316 }
317
318 private boolean isFull() {
319 return maxFiles < openFiles.get() || maxBytes < openBytes.get();
320 }
321
322 private long toStart(final long offset) {
323 return (offset >>> windowSizeShift) << windowSizeShift;
324 }
325
326 private static int tableSize(final WindowCacheConfig cfg) {
327 final int wsz = cfg.getPackedGitWindowSize();
328 final long limit = cfg.getPackedGitLimit();
329 if (wsz <= 0)
330 throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
331 if (limit < wsz)
332 throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
333 return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
334 }
335
336 private static int lockCount(final WindowCacheConfig cfg) {
337 return Math.max(cfg.getPackedGitOpenFiles(), 32);
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352 private ByteWindow getOrLoad(final PackFile pack, final long position)
353 throws IOException {
354 final int slot = slot(pack, position);
355 final Entry e1 = table.get(slot);
356 ByteWindow v = scan(e1, pack, position);
357 if (v != null)
358 return v;
359
360 synchronized (lock(pack, position)) {
361 Entry e2 = table.get(slot);
362 if (e2 != e1) {
363 v = scan(e2, pack, position);
364 if (v != null)
365 return v;
366 }
367
368 v = load(pack, position);
369 final Ref ref = createRef(pack, position, v);
370 hit(ref);
371 for (;;) {
372 final Entry n = new Entry(clean(e2), ref);
373 if (table.compareAndSet(slot, e2, n))
374 break;
375 e2 = table.get(slot);
376 }
377 }
378
379 if (evictLock.tryLock()) {
380 try {
381 gc();
382 evict();
383 } finally {
384 evictLock.unlock();
385 }
386 }
387
388 return v;
389 }
390
391 private ByteWindow scan(Entry n, final PackFile pack, final long position) {
392 for (; n != null; n = n.next) {
393 final Ref r = n.ref;
394 if (r.pack == pack && r.position == position) {
395 final ByteWindow v = r.get();
396 if (v != null) {
397 hit(r);
398 return v;
399 }
400 n.kill();
401 break;
402 }
403 }
404 return null;
405 }
406
407 private void hit(final Ref r) {
408
409
410
411
412
413
414
415 final long c = clock.get();
416 clock.compareAndSet(c, c + 1);
417 r.lastAccess = c;
418 }
419
420 private void evict() {
421 while (isFull()) {
422 int ptr = rng.nextInt(tableSize);
423 Entry old = null;
424 int slot = 0;
425 for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
426 if (tableSize <= ptr)
427 ptr = 0;
428 for (Entry e = table.get(ptr); e != null; e = e.next) {
429 if (e.dead)
430 continue;
431 if (old == null || e.ref.lastAccess < old.ref.lastAccess) {
432 old = e;
433 slot = ptr;
434 }
435 }
436 }
437 if (old != null) {
438 old.kill();
439 gc();
440 final Entry e1 = table.get(slot);
441 table.compareAndSet(slot, e1, clean(e1));
442 }
443 }
444 }
445
446
447
448
449
450
451
452
453
454
455
456 private void removeAll() {
457 for (int s = 0; s < tableSize; s++) {
458 Entry e1;
459 do {
460 e1 = table.get(s);
461 for (Entry e = e1; e != null; e = e.next)
462 e.kill();
463 } while (!table.compareAndSet(s, e1, null));
464 }
465 gc();
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479 private void removeAll(final PackFile pack) {
480 for (int s = 0; s < tableSize; s++) {
481 final Entry e1 = table.get(s);
482 boolean hasDead = false;
483 for (Entry e = e1; e != null; e = e.next) {
484 if (e.ref.pack == pack) {
485 e.kill();
486 hasDead = true;
487 } else if (e.dead)
488 hasDead = true;
489 }
490 if (hasDead)
491 table.compareAndSet(s, e1, clean(e1));
492 }
493 gc();
494 }
495
496 private void gc() {
497 Ref r;
498 while ((r = (Ref) queue.poll()) != null) {
499
500
501
502
503
504
505
506
507
508
509 if (r.canClear()) {
510 clear(r);
511
512 boolean found = false;
513 final int s = slot(r.pack, r.position);
514 final Entry e1 = table.get(s);
515 for (Entry n = e1; n != null; n = n.next) {
516 if (n.ref == r) {
517 n.dead = true;
518 found = true;
519 break;
520 }
521 }
522 if (found)
523 table.compareAndSet(s, e1, clean(e1));
524 }
525 }
526 }
527
528 private int slot(final PackFile pack, final long position) {
529 return (hash(pack.hash, position) >>> 1) % tableSize;
530 }
531
532 private Lock lock(final PackFile pack, final long position) {
533 return locks[(hash(pack.hash, position) >>> 1) % locks.length];
534 }
535
536 private static Entry clean(Entry top) {
537 while (top != null && top.dead) {
538 top.ref.enqueue();
539 top = top.next;
540 }
541 if (top == null)
542 return null;
543 final Entry n = clean(top.next);
544 return n == top.next ? top : new Entry(n, top.ref);
545 }
546
547 private static class Entry {
548
549 final Entry next;
550
551
552 final Ref ref;
553
554
555
556
557
558
559
560
561 volatile boolean dead;
562
563 Entry(final Entry n, final Ref r) {
564 next = n;
565 ref = r;
566 }
567
568 final void kill() {
569 dead = true;
570 ref.enqueue();
571 }
572 }
573
574
575 private static class Ref extends SoftReference<ByteWindow> {
576 final PackFile pack;
577
578 final long position;
579
580 final int size;
581
582 long lastAccess;
583
584 private boolean cleared;
585
586 protected Ref(final PackFile pack, final long position,
587 final ByteWindow v, final ReferenceQueue<ByteWindow> queue) {
588 super(v, queue);
589 this.pack = pack;
590 this.position = position;
591 this.size = v.size();
592 }
593
594 final synchronized boolean canClear() {
595 if (cleared)
596 return false;
597 cleared = true;
598 return true;
599 }
600 }
601
602 private static final class Lock {
603
604 }
605 }