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