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.dfs;
46
47 import java.io.IOException;
48 import java.util.concurrent.atomic.AtomicLong;
49 import java.util.concurrent.atomic.AtomicReference;
50 import java.util.concurrent.atomic.AtomicReferenceArray;
51 import java.util.concurrent.locks.ReentrantLock;
52 import java.util.stream.LongStream;
53
54 import org.eclipse.jgit.annotations.Nullable;
55 import org.eclipse.jgit.internal.JGitText;
56 import org.eclipse.jgit.internal.storage.pack.PackExt;
57
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 public final class DfsBlockCache {
94 private static volatile DfsBlockCache cache;
95
96 static {
97 reconfigure(new DfsBlockCacheConfig());
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112 public static void reconfigure(DfsBlockCacheConfig cfg) {
113 cache = new DfsBlockCache(cfg);
114 }
115
116
117
118
119
120
121 public static DfsBlockCache getInstance() {
122 return cache;
123 }
124
125
126 private final int tableSize;
127
128
129 private final AtomicReferenceArray<HashEntry> table;
130
131
132 private final ReentrantLock[] loadLocks;
133
134
135 private final long maxBytes;
136
137
138 private final long maxStreamThroughCache;
139
140
141
142
143
144
145
146
147
148
149
150 private final int blockSize;
151
152
153 private final int blockSizeShift;
154
155
156
157
158 private final AtomicReference<AtomicLong[]> statHit;
159
160
161
162
163
164 private final AtomicReference<AtomicLong[]> statMiss;
165
166
167
168
169
170 private final AtomicReference<AtomicLong[]> statEvict;
171
172
173
174
175 private final AtomicReference<AtomicLong[]> liveBytes;
176
177
178 private final ReentrantLock clockLock;
179
180
181 private Ref clockHand;
182
183 @SuppressWarnings("unchecked")
184 private DfsBlockCache(DfsBlockCacheConfig cfg) {
185 tableSize = tableSize(cfg);
186 if (tableSize < 1)
187 throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
188
189 table = new AtomicReferenceArray<>(tableSize);
190 loadLocks = new ReentrantLock[cfg.getConcurrencyLevel()];
191 for (int i = 0; i < loadLocks.length; i++)
192 loadLocks[i] = new ReentrantLock(true );
193
194 maxBytes = cfg.getBlockLimit();
195 maxStreamThroughCache = (long) (maxBytes * cfg.getStreamRatio());
196 blockSize = cfg.getBlockSize();
197 blockSizeShift = Integer.numberOfTrailingZeros(blockSize);
198
199 clockLock = new ReentrantLock(true );
200 String none = "";
201 clockHand = new Ref<>(
202 DfsStreamKey.of(new DfsRepositoryDescription(none), none, null),
203 -1, 0, null);
204 clockHand.next = clockHand;
205
206 statHit = new AtomicReference<>(newCounters());
207 statMiss = new AtomicReference<>(newCounters());
208 statEvict = new AtomicReference<>(newCounters());
209 liveBytes = new AtomicReference<>(newCounters());
210 }
211
212 boolean shouldCopyThroughCache(long length) {
213 return length <= maxStreamThroughCache;
214 }
215
216
217
218
219
220
221 public long[] getCurrentSize() {
222 return getStatVals(liveBytes);
223 }
224
225
226
227
228
229
230 public long getFillPercentage() {
231 return LongStream.of(getCurrentSize()).sum() * 100 / maxBytes;
232 }
233
234
235
236
237
238
239
240 public long[] getHitCount() {
241 return getStatVals(statHit);
242 }
243
244
245
246
247
248
249
250
251 public long[] getMissCount() {
252 return getStatVals(statMiss);
253 }
254
255
256
257
258
259
260 public long[] getTotalRequestCount() {
261 AtomicLong[] hit = statHit.get();
262 AtomicLong[] miss = statMiss.get();
263 long[] cnt = new long[Math.max(hit.length, miss.length)];
264 for (int i = 0; i < hit.length; i++) {
265 cnt[i] += hit[i].get();
266 }
267 for (int i = 0; i < miss.length; i++) {
268 cnt[i] += miss[i].get();
269 }
270 return cnt;
271 }
272
273
274
275
276
277
278 public long[] getHitRatio() {
279 AtomicLong[] hit = statHit.get();
280 AtomicLong[] miss = statMiss.get();
281 long[] ratio = new long[Math.max(hit.length, miss.length)];
282 for (int i = 0; i < ratio.length; i++) {
283 if (i >= hit.length) {
284 ratio[i] = 0;
285 } else if (i >= miss.length) {
286 ratio[i] = 100;
287 } else {
288 long hitVal = hit[i].get();
289 long missVal = miss[i].get();
290 long total = hitVal + missVal;
291 ratio[i] = total == 0 ? 0 : hitVal * 100 / total;
292 }
293 }
294 return ratio;
295 }
296
297
298
299
300
301
302
303
304 public long[] getEvictions() {
305 return getStatVals(statEvict);
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319 public boolean hasBlock0(DfsStreamKey key) {
320 HashEntry e1 = table.get(slot(key, 0));
321 DfsBlock v = scan(e1, key, 0);
322 return v != null && v.contains(key, 0);
323 }
324
325 private int hash(int packHash, long off) {
326 return packHash + (int) (off >>> blockSizeShift);
327 }
328
329 int getBlockSize() {
330 return blockSize;
331 }
332
333 private static int tableSize(DfsBlockCacheConfig cfg) {
334 final int wsz = cfg.getBlockSize();
335 final long limit = cfg.getBlockLimit();
336 if (wsz <= 0)
337 throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
338 if (limit < wsz)
339 throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
340 return (int) Math.min(5 * (limit / wsz) / 2, Integer.MAX_VALUE);
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx,
359 @Nullable ReadableChannel fileChannel) throws IOException {
360 final long requestedPosition = position;
361 position = file.alignToBlock(position);
362
363 DfsStreamKey key = file.key;
364 int slot = slot(key, position);
365 HashEntry e1 = table.get(slot);
366 DfsBlock v = scan(e1, key, position);
367 if (v != null && v.contains(key, requestedPosition)) {
368 ctx.stats.blockCacheHit++;
369 getStat(statHit, key).incrementAndGet();
370 return v;
371 }
372
373 reserveSpace(blockSize, key);
374 ReentrantLock regionLock = lockFor(key, position);
375 regionLock.lock();
376 try {
377 HashEntry e2 = table.get(slot);
378 if (e2 != e1) {
379 v = scan(e2, key, position);
380 if (v != null) {
381 ctx.stats.blockCacheHit++;
382 getStat(statHit, key).incrementAndGet();
383 creditSpace(blockSize, key);
384 return v;
385 }
386 }
387
388 getStat(statMiss, key).incrementAndGet();
389 boolean credit = true;
390 try {
391 v = file.readOneBlock(requestedPosition, ctx, fileChannel);
392 credit = false;
393 } finally {
394 if (credit)
395 creditSpace(blockSize, key);
396 }
397 if (position != v.start) {
398
399 position = v.start;
400 slot = slot(key, position);
401 e2 = table.get(slot);
402 }
403
404 Ref<DfsBlock> ref = new Ref<>(key, position, v.size(), v);
405 ref.hot = true;
406 for (;;) {
407 HashEntry n = new HashEntry(clean(e2), ref);
408 if (table.compareAndSet(slot, e2, n))
409 break;
410 e2 = table.get(slot);
411 }
412 addToClock(ref, blockSize - v.size());
413 } finally {
414 regionLock.unlock();
415 }
416
417
418
419 if (v.contains(file.key, requestedPosition))
420 return v;
421 return getOrLoad(file, requestedPosition, ctx, fileChannel);
422 }
423
424 @SuppressWarnings("unchecked")
425 private void reserveSpace(int reserve, DfsStreamKey key) {
426 clockLock.lock();
427 try {
428 long live = LongStream.of(getCurrentSize()).sum() + reserve;
429 if (maxBytes < live) {
430 Ref prev = clockHand;
431 Ref hand = clockHand.next;
432 do {
433 if (hand.hot) {
434
435
436 hand.hot = false;
437 prev = hand;
438 hand = hand.next;
439 continue;
440 } else if (prev == hand)
441 break;
442
443
444
445 Ref dead = hand;
446 hand = hand.next;
447 prev.next = hand;
448 dead.next = null;
449 dead.value = null;
450 live -= dead.size;
451 getStat(liveBytes, dead.key).addAndGet(-dead.size);
452 getStat(statEvict, dead.key).incrementAndGet();
453 } while (maxBytes < live);
454 clockHand = prev;
455 }
456 getStat(liveBytes, key).addAndGet(reserve);
457 } finally {
458 clockLock.unlock();
459 }
460 }
461
462 private void creditSpace(int credit, DfsStreamKey key) {
463 clockLock.lock();
464 try {
465 getStat(liveBytes, key).addAndGet(-credit);
466 } finally {
467 clockLock.unlock();
468 }
469 }
470
471 @SuppressWarnings("unchecked")
472 private void addToClock(Ref ref, int credit) {
473 clockLock.lock();
474 try {
475 if (credit != 0) {
476 getStat(liveBytes, ref.key).addAndGet(-credit);
477 }
478 Ref ptr = clockHand;
479 ref.next = ptr.next;
480 ptr.next = ref;
481 clockHand = ref;
482 } finally {
483 clockLock.unlock();
484 }
485 }
486
487 void put(DfsBlock v) {
488 put(v.stream, v.start, v.size(), v);
489 }
490
491 <T> Ref<T> putRef(DfsStreamKey key, long size, T v) {
492 return put(key, 0, (int) Math.min(size, Integer.MAX_VALUE), v);
493 }
494
495 <T> Ref<T> put(DfsStreamKey key, long pos, int size, T v) {
496 int slot = slot(key, pos);
497 HashEntry e1 = table.get(slot);
498 Ref<T> ref = scanRef(e1, key, pos);
499 if (ref != null)
500 return ref;
501
502 reserveSpace(size, key);
503 ReentrantLock regionLock = lockFor(key, pos);
504 regionLock.lock();
505 try {
506 HashEntry e2 = table.get(slot);
507 if (e2 != e1) {
508 ref = scanRef(e2, key, pos);
509 if (ref != null) {
510 creditSpace(size, key);
511 return ref;
512 }
513 }
514
515 ref = new Ref<>(key, pos, size, v);
516 ref.hot = true;
517 for (;;) {
518 HashEntry n = new HashEntry(clean(e2), ref);
519 if (table.compareAndSet(slot, e2, n))
520 break;
521 e2 = table.get(slot);
522 }
523 addToClock(ref, 0);
524 } finally {
525 regionLock.unlock();
526 }
527 return ref;
528 }
529
530 boolean contains(DfsStreamKey key, long position) {
531 return scan(table.get(slot(key, position)), key, position) != null;
532 }
533
534 @SuppressWarnings("unchecked")
535 <T> T get(DfsStreamKey key, long position) {
536 T val = (T) scan(table.get(slot(key, position)), key, position);
537 if (val == null)
538 getStat(statMiss, key).incrementAndGet();
539 else
540 getStat(statHit, key).incrementAndGet();
541 return val;
542 }
543
544 private <T> T scan(HashEntry n, DfsStreamKey key, long position) {
545 Ref<T> r = scanRef(n, key, position);
546 return r != null ? r.get() : null;
547 }
548
549 <T> Ref<T> getRef(DfsStreamKey key) {
550 Ref<T> r = scanRef(table.get(slot(key, 0)), key, 0);
551 if (r != null)
552 getStat(statHit, key).incrementAndGet();
553 else
554 getStat(statMiss, key).incrementAndGet();
555 return r;
556 }
557
558 @SuppressWarnings("unchecked")
559 private <T> Ref<T> scanRef(HashEntry n, DfsStreamKey key, long position) {
560 for (; n != null; n = n.next) {
561 Ref<T> r = n.ref;
562 if (r.position == position && r.key.equals(key))
563 return r.get() != null ? r : null;
564 }
565 return null;
566 }
567
568 private int slot(DfsStreamKey key, long position) {
569 return (hash(key.hash, position) >>> 1) % tableSize;
570 }
571
572 private ReentrantLock lockFor(DfsStreamKey key, long position) {
573 return loadLocks[(hash(key.hash, position) >>> 1) % loadLocks.length];
574 }
575
576 private static AtomicLong[] newCounters() {
577 AtomicLong[] ret = new AtomicLong[PackExt.values().length];
578 for (int i = 0; i < ret.length; i++) {
579 ret[i] = new AtomicLong();
580 }
581 return ret;
582 }
583
584 private static AtomicLong getStat(AtomicReference<AtomicLong[]> stats,
585 DfsStreamKey key) {
586 int pos = key.packExtPos;
587 while (true) {
588 AtomicLong[] vals = stats.get();
589 if (pos < vals.length) {
590 return vals[pos];
591 }
592 AtomicLong[] expect = vals;
593 vals = new AtomicLong[Math.max(pos + 1, PackExt.values().length)];
594 System.arraycopy(expect, 0, vals, 0, expect.length);
595 for (int i = expect.length; i < vals.length; i++) {
596 vals[i] = new AtomicLong();
597 }
598 if (stats.compareAndSet(expect, vals)) {
599 return vals[pos];
600 }
601 }
602 }
603
604 private static long[] getStatVals(AtomicReference<AtomicLong[]> stat) {
605 AtomicLong[] stats = stat.get();
606 long[] cnt = new long[stats.length];
607 for (int i = 0; i < stats.length; i++) {
608 cnt[i] = stats[i].get();
609 }
610 return cnt;
611 }
612
613 private static HashEntry clean(HashEntry top) {
614 while (top != null && top.ref.next == null)
615 top = top.next;
616 if (top == null)
617 return null;
618 HashEntry n = clean(top.next);
619 return n == top.next ? top : new HashEntry(n, top.ref);
620 }
621
622 private static final class HashEntry {
623
624 final HashEntry next;
625
626
627 final Ref ref;
628
629 HashEntry(HashEntry n, Ref r) {
630 next = n;
631 ref = r;
632 }
633 }
634
635 static final class Ref<T> {
636 final DfsStreamKey key;
637 final long position;
638 final int size;
639 volatile T value;
640 Ref next;
641 volatile boolean hot;
642
643 Ref(DfsStreamKey key, long position, int size, T v) {
644 this.key = key;
645 this.position = position;
646 this.size = size;
647 this.value = v;
648 }
649
650 T get() {
651 T v = value;
652 if (v != null)
653 hot = true;
654 return v;
655 }
656
657 boolean has() {
658 return value != null;
659 }
660 }
661 }