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 WindowCacheal/storage/file/WindowCache.html#WindowCache">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 | RuntimeException | Error e) {
302 close(pack);
303 throw e;
304 }
305 }
306
307 private Ref createRef(PackFile p, long o, ByteWindow v) {
308 final Ref ref = new Ref(p, o, v, queue);
309 openBytes.addAndGet(ref.size);
310 return ref;
311 }
312
313 private void clear(Ref ref) {
314 openBytes.addAndGet(-ref.size);
315 close(ref.pack);
316 }
317
318 private void close(PackFile pack) {
319 if (pack.endWindowCache())
320 openFiles.decrementAndGet();
321 }
322
323 private boolean isFull() {
324 return maxFiles < openFiles.get() || maxBytes < openBytes.get();
325 }
326
327 private long toStart(long offset) {
328 return (offset >>> windowSizeShift) << windowSizeShift;
329 }
330
331 private static int tableSize(WindowCacheConfig cfg) {
332 final int wsz = cfg.getPackedGitWindowSize();
333 final long limit = cfg.getPackedGitLimit();
334 if (wsz <= 0)
335 throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
336 if (limit < wsz)
337 throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
338 return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
339 }
340
341 private static int lockCount(WindowCacheConfig cfg) {
342 return Math.max(cfg.getPackedGitOpenFiles(), 32);
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357 private ByteWindow getOrLoad(PackFile pack, long position)
358 throws IOException {
359 final int slot = slot(pack, position);
360 final Entry e1 = table.get(slot);
361 ByteWindow v = scan(e1, pack, position);
362 if (v != null)
363 return v;
364
365 synchronized (lock(pack, position)) {
366 Entry e2 = table.get(slot);
367 if (e2 != e1) {
368 v = scan(e2, pack, position);
369 if (v != null)
370 return v;
371 }
372
373 v = load(pack, position);
374 final Ref ref = createRef(pack, position, v);
375 hit(ref);
376 for (;;) {
377 final Entry n = new Entry(clean(e2), ref);
378 if (table.compareAndSet(slot, e2, n))
379 break;
380 e2 = table.get(slot);
381 }
382 }
383
384 if (evictLock.tryLock()) {
385 try {
386 gc();
387 evict();
388 } finally {
389 evictLock.unlock();
390 }
391 }
392
393 return v;
394 }
395
396 private ByteWindow scan(Entry n, PackFile pack, long position) {
397 for (; n != null; n = n.next) {
398 final Ref r = n.ref;
399 if (r.pack == pack && r.position == position) {
400 final ByteWindow v = r.get();
401 if (v != null) {
402 hit(r);
403 return v;
404 }
405 n.kill();
406 break;
407 }
408 }
409 return null;
410 }
411
412 private void hit(Ref r) {
413
414
415
416
417
418
419
420 final long c = clock.get();
421 clock.compareAndSet(c, c + 1);
422 r.lastAccess = c;
423 }
424
425 private void evict() {
426 while (isFull()) {
427 int ptr = rng.nextInt(tableSize);
428 Entry old = null;
429 int slot = 0;
430 for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
431 if (tableSize <= ptr)
432 ptr = 0;
433 for (Entry e = table.get(ptr); e != null; e = e.next) {
434 if (e.dead)
435 continue;
436 if (old == null || e.ref.lastAccess < old.ref.lastAccess) {
437 old = e;
438 slot = ptr;
439 }
440 }
441 }
442 if (old != null) {
443 old.kill();
444 gc();
445 final Entry e1 = table.get(slot);
446 table.compareAndSet(slot, e1, clean(e1));
447 }
448 }
449 }
450
451
452
453
454
455
456
457
458
459
460
461 private void removeAll() {
462 for (int s = 0; s < tableSize; s++) {
463 Entry e1;
464 do {
465 e1 = table.get(s);
466 for (Entry e = e1; e != null; e = e.next)
467 e.kill();
468 } while (!table.compareAndSet(s, e1, null));
469 }
470 gc();
471 }
472
473
474
475
476
477
478
479
480
481
482
483
484 private void removeAll(PackFile pack) {
485 for (int s = 0; s < tableSize; s++) {
486 final Entry e1 = table.get(s);
487 boolean hasDead = false;
488 for (Entry e = e1; e != null; e = e.next) {
489 if (e.ref.pack == pack) {
490 e.kill();
491 hasDead = true;
492 } else if (e.dead)
493 hasDead = true;
494 }
495 if (hasDead)
496 table.compareAndSet(s, e1, clean(e1));
497 }
498 gc();
499 }
500
501 private void gc() {
502 Ref r;
503 while ((r = (Ref) queue.poll()) != null) {
504 clear(r);
505
506 final int s = slot(r.pack, r.position);
507 final Entry e1 = table.get(s);
508 for (Entry n = e1; n != null; n = n.next) {
509 if (n.ref == r) {
510 n.dead = true;
511 table.compareAndSet(s, e1, clean(e1));
512 break;
513 }
514 }
515 }
516 }
517
518 private int slot(PackFile pack, long position) {
519 return (hash(pack.hash, position) >>> 1) % tableSize;
520 }
521
522 private Lock lock(PackFile pack, long position) {
523 return locks[(hash(pack.hash, position) >>> 1) % locks.length];
524 }
525
526 private static Entry clean(Entry top) {
527 while (top != null && top.dead) {
528 top.ref.enqueue();
529 top = top.next;
530 }
531 if (top == null)
532 return null;
533 final Entry n = clean(top.next);
534 return n == top.next ? top : new Entry(n, top.ref);
535 }
536
537 private static class Entry {
538
539 final Entry next;
540
541
542 final Ref ref;
543
544
545
546
547
548
549
550
551 volatile boolean dead;
552
553 Entry(Entry n, Ref r) {
554 next = n;
555 ref = r;
556 }
557
558 final void kill() {
559 dead = true;
560 ref.enqueue();
561 }
562 }
563
564
565 private static class Ref extends SoftReference<ByteWindow> {
566 final PackFile pack;
567
568 final long position;
569
570 final int size;
571
572 long lastAccess;
573
574 protected Ref(final PackFile pack, final long position,
575 final ByteWindow v, final ReferenceQueue<ByteWindow> queue) {
576 super(v, queue);
577 this.pack = pack;
578 this.position = position;
579 this.size = v.size();
580 }
581 }
582
583 private static final class Lock {
584
585 }
586 }