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.Collections;
51 import java.util.Map;
52 import java.util.Random;
53 import java.util.concurrent.ConcurrentHashMap;
54 import java.util.concurrent.ConcurrentLinkedQueue;
55 import java.util.concurrent.atomic.AtomicLong;
56 import java.util.concurrent.atomic.AtomicReferenceArray;
57 import java.util.concurrent.atomic.LongAdder;
58 import java.util.concurrent.locks.ReentrantLock;
59 import java.util.stream.Collectors;
60
61 import org.eclipse.jgit.annotations.NonNull;
62 import org.eclipse.jgit.internal.JGitText;
63 import org.eclipse.jgit.storage.file.WindowCacheConfig;
64 import org.eclipse.jgit.storage.file.WindowCacheStats;
65 import org.eclipse.jgit.util.Monitoring;
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 public class WindowCache {
142
143
144
145
146 static interface StatsRecorder {
147
148
149
150
151
152
153 void recordHits(int count);
154
155
156
157
158
159
160
161
162 void recordMisses(int count);
163
164
165
166
167
168
169
170 void recordLoadSuccess(long loadTimeNanos);
171
172
173
174
175
176
177
178 void recordLoadFailure(long loadTimeNanos);
179
180
181
182
183
184
185
186 void recordEvictions(int count);
187
188
189
190
191
192
193
194 void recordOpenFiles(int delta);
195
196
197
198
199
200
201
202
203
204
205 void recordOpenBytes(PackFile pack, int delta);
206
207
208
209
210
211
212
213 @NonNull
214 WindowCacheStats getStats();
215 }
216
217 static class StatsRecorderImpl
218 implements StatsRecorder, WindowCacheStats {
219 private final LongAdder hitCount;
220 private final LongAdder missCount;
221 private final LongAdder loadSuccessCount;
222 private final LongAdder loadFailureCount;
223 private final LongAdder totalLoadTime;
224 private final LongAdder evictionCount;
225 private final LongAdder openFileCount;
226 private final LongAdder openByteCount;
227 private final Map<String, LongAdder> openByteCountPerRepository;
228
229
230
231
232 public StatsRecorderImpl() {
233 hitCount = new LongAdder();
234 missCount = new LongAdder();
235 loadSuccessCount = new LongAdder();
236 loadFailureCount = new LongAdder();
237 totalLoadTime = new LongAdder();
238 evictionCount = new LongAdder();
239 openFileCount = new LongAdder();
240 openByteCount = new LongAdder();
241 openByteCountPerRepository = new ConcurrentHashMap<>();
242 }
243
244 @Override
245 public void recordHits(int count) {
246 hitCount.add(count);
247 }
248
249 @Override
250 public void recordMisses(int count) {
251 missCount.add(count);
252 }
253
254 @Override
255 public void recordLoadSuccess(long loadTimeNanos) {
256 loadSuccessCount.increment();
257 totalLoadTime.add(loadTimeNanos);
258 }
259
260 @Override
261 public void recordLoadFailure(long loadTimeNanos) {
262 loadFailureCount.increment();
263 totalLoadTime.add(loadTimeNanos);
264 }
265
266 @Override
267 public void recordEvictions(int count) {
268 evictionCount.add(count);
269 }
270
271 @Override
272 public void recordOpenFiles(int delta) {
273 openFileCount.add(delta);
274 }
275
276 @Override
277 public void recordOpenBytes(PackFile pack, int delta) {
278 openByteCount.add(delta);
279 String repositoryId = repositoryId(pack);
280 LongAdder la = openByteCountPerRepository
281 .computeIfAbsent(repositoryId, k -> new LongAdder());
282 la.add(delta);
283 if (delta < 0) {
284 openByteCountPerRepository.computeIfPresent(repositoryId,
285 (k, v) -> v.longValue() == 0 ? null : v);
286 }
287 }
288
289 private static String repositoryId(PackFile pack) {
290
291
292 return pack.getPackFile().getParentFile().getParentFile()
293 .getParent();
294 }
295
296 @Override
297 public WindowCacheStats getStats() {
298 return this;
299 }
300
301 @Override
302 public long getHitCount() {
303 return hitCount.sum();
304 }
305
306 @Override
307 public long getMissCount() {
308 return missCount.sum();
309 }
310
311 @Override
312 public long getLoadSuccessCount() {
313 return loadSuccessCount.sum();
314 }
315
316 @Override
317 public long getLoadFailureCount() {
318 return loadFailureCount.sum();
319 }
320
321 @Override
322 public long getEvictionCount() {
323 return evictionCount.sum();
324 }
325
326 @Override
327 public long getTotalLoadTime() {
328 return totalLoadTime.sum();
329 }
330
331 @Override
332 public long getOpenFileCount() {
333 return openFileCount.sum();
334 }
335
336 @Override
337 public long getOpenByteCount() {
338 return openByteCount.sum();
339 }
340
341 @Override
342 public void resetCounters() {
343 hitCount.reset();
344 missCount.reset();
345 loadSuccessCount.reset();
346 loadFailureCount.reset();
347 totalLoadTime.reset();
348 evictionCount.reset();
349 }
350
351 @Override
352 public Map<String, Long> getOpenByteCountPerRepository() {
353 return Collections.unmodifiableMap(
354 openByteCountPerRepository.entrySet().stream()
355 .collect(Collectors.toMap(Map.Entry::getKey,
356 e -> Long.valueOf(e.getValue().sum()),
357 (u, v) -> v)));
358 }
359 }
360
361 private static final int bits(int newSize) {
362 if (newSize < 4096)
363 throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
364 if (Integer.bitCount(newSize) != 1)
365 throw new IllegalArgumentException(JGitText.get().windowSizeMustBePowerOf2);
366 return Integer.numberOfTrailingZeros(newSize);
367 }
368
369 private static final Random rng = new Random();
370
371 private static volatile WindowCache cache;
372
373 private static volatile int streamFileThreshold;
374
375 static {
376 reconfigure(new WindowCacheConfig());
377 }
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 @Deprecated
394 public static void reconfigure(WindowCacheConfig cfg) {
395 final WindowCacheal/storage/file/WindowCache.html#WindowCache">WindowCache nc = new WindowCache(cfg);
396 final WindowCache oc = cache;
397 if (oc != null)
398 oc.removeAll();
399 cache = nc;
400 streamFileThreshold = cfg.getStreamFileThreshold();
401 DeltaBaseCache.reconfigure(cfg);
402 }
403
404 static int getStreamFileThreshold() {
405 return streamFileThreshold;
406 }
407
408
409
410
411 public static WindowCache getInstance() {
412 return cache;
413 }
414
415 static final ByteWindow get(PackFile pack, long offset)
416 throws IOException {
417 final WindowCache c = cache;
418 final ByteWindow r = c.getOrLoad(pack, c.toStart(offset));
419 if (c != cache) {
420
421
422
423
424
425 c.removeAll();
426 }
427 return r;
428 }
429
430 static final void purge(PackFile pack) {
431 cache.removeAll(pack);
432 }
433
434
435 private final CleanupQueue queue;
436
437
438 private final int tableSize;
439
440
441 private final AtomicLong clock;
442
443
444 private final AtomicReferenceArray<Entry> table;
445
446
447 private final Lock[] locks;
448
449
450 private final ReentrantLock evictLock;
451
452
453 private final int evictBatch;
454
455 private final int maxFiles;
456
457 private final long maxBytes;
458
459 private final boolean mmap;
460
461 private final int windowSizeShift;
462
463 private final int windowSize;
464
465 private final StatsRecorder statsRecorder;
466
467 private final StatsRecorderImpl mbean;
468
469 private boolean useStrongRefs;
470
471 private WindowCache(WindowCacheConfig cfg) {
472 tableSize = tableSize(cfg);
473 final int lockCount = lockCount(cfg);
474 if (tableSize < 1)
475 throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
476 if (lockCount < 1)
477 throw new IllegalArgumentException(JGitText.get().lockCountMustBeGreaterOrEqual1);
478
479 clock = new AtomicLong(1);
480 table = new AtomicReferenceArray<>(tableSize);
481 locks = new Lock[lockCount];
482 for (int i = 0; i < locks.length; i++)
483 locks[i] = new Lock();
484 evictLock = new ReentrantLock();
485
486 int eb = (int) (tableSize * .1);
487 if (64 < eb)
488 eb = 64;
489 else if (eb < 4)
490 eb = 4;
491 if (tableSize < eb)
492 eb = tableSize;
493 evictBatch = eb;
494
495 maxFiles = cfg.getPackedGitOpenFiles();
496 maxBytes = cfg.getPackedGitLimit();
497 mmap = cfg.isPackedGitMMAP();
498 windowSizeShift = bits(cfg.getPackedGitWindowSize());
499 windowSize = 1 << windowSizeShift;
500 useStrongRefs = cfg.isPackedGitUseStrongRefs();
501 queue = useStrongRefs ? new StrongCleanupQueue(this)
502 : new SoftCleanupQueue(this);
503
504 mbean = new StatsRecorderImpl();
505 statsRecorder = mbean;
506 Monitoring.registerMBean(mbean, "block_cache");
507
508 if (maxFiles < 1)
509 throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
510 if (maxBytes < windowSize)
511 throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
512 }
513
514
515
516
517 public WindowCacheStats getStats() {
518 return statsRecorder.getStats();
519 }
520
521
522
523
524 public void resetStats() {
525 mbean.resetCounters();
526 }
527
528 private int hash(int packHash, long off) {
529 return packHash + (int) (off >>> windowSizeShift);
530 }
531
532 private ByteWindow load(PackFile pack, long offset) throws IOException {
533 long startTime = System.nanoTime();
534 if (pack.beginWindowCache())
535 statsRecorder.recordOpenFiles(1);
536 try {
537 if (mmap)
538 return pack.mmap(offset, windowSize);
539 ByteArrayWindow w = pack.read(offset, windowSize);
540 statsRecorder.recordLoadSuccess(System.nanoTime() - startTime);
541 return w;
542 } catch (IOException | RuntimeException | Error e) {
543 close(pack);
544 statsRecorder.recordLoadFailure(System.nanoTime() - startTime);
545 throw e;
546 } finally {
547 statsRecorder.recordMisses(1);
548 }
549 }
550
551 private PageRef<ByteWindow> createRef(PackFile p, long o, ByteWindow v) {
552 final PageRef<ByteWindow> ref = useStrongRefs
553 ? new StrongRef(p, o, v, queue)
554 : new SoftRef(p, o, v, (SoftCleanupQueue) queue);
555 statsRecorder.recordOpenBytes(ref.getPack(), ref.getSize());
556 return ref;
557 }
558
559 private void clear(PageRef<ByteWindow> ref) {
560 statsRecorder.recordOpenBytes(ref.getPack(), -ref.getSize());
561 statsRecorder.recordEvictions(1);
562 close(ref.getPack());
563 }
564
565 private void close(PackFile pack) {
566 if (pack.endWindowCache()) {
567 statsRecorder.recordOpenFiles(-1);
568 }
569 }
570
571 private boolean isFull() {
572 return maxFiles < mbean.getOpenFileCount()
573 || maxBytes < mbean.getOpenByteCount();
574 }
575
576 private long toStart(long offset) {
577 return (offset >>> windowSizeShift) << windowSizeShift;
578 }
579
580 private static int tableSize(WindowCacheConfig cfg) {
581 final int wsz = cfg.getPackedGitWindowSize();
582 final long limit = cfg.getPackedGitLimit();
583 if (wsz <= 0)
584 throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
585 if (limit < wsz)
586 throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
587 return (int) Math.min(5 * (limit / wsz) / 2, 2000000000);
588 }
589
590 private static int lockCount(WindowCacheConfig cfg) {
591 return Math.max(cfg.getPackedGitOpenFiles(), 32);
592 }
593
594
595
596
597
598
599
600
601
602
603
604
605
606 private ByteWindow getOrLoad(PackFile pack, long position)
607 throws IOException {
608 final int slot = slot(pack, position);
609 final Entry e1 = table.get(slot);
610 ByteWindow v = scan(e1, pack, position);
611 if (v != null) {
612 statsRecorder.recordHits(1);
613 return v;
614 }
615
616 synchronized (lock(pack, position)) {
617 Entry e2 = table.get(slot);
618 if (e2 != e1) {
619 v = scan(e2, pack, position);
620 if (v != null) {
621 statsRecorder.recordHits(1);
622 return v;
623 }
624 }
625
626 v = load(pack, position);
627 final PageRef<ByteWindow> ref = createRef(pack, position, v);
628 hit(ref);
629 for (;;) {
630 final Entry n = new Entry(clean(e2), ref);
631 if (table.compareAndSet(slot, e2, n))
632 break;
633 e2 = table.get(slot);
634 }
635 }
636
637 if (evictLock.tryLock()) {
638 try {
639 gc();
640 evict();
641 } finally {
642 evictLock.unlock();
643 }
644 }
645
646 return v;
647 }
648
649 private ByteWindow scan(Entry n, PackFile pack, long position) {
650 for (; n != null; n = n.next) {
651 final PageRef<ByteWindow> r = n.ref;
652 if (r.getPack() == pack && r.getPosition() == position) {
653 final ByteWindow v = r.get();
654 if (v != null) {
655 hit(r);
656 return v;
657 }
658 n.kill();
659 break;
660 }
661 }
662 return null;
663 }
664
665 private void hit(PageRef r) {
666
667
668
669
670
671
672
673 final long c = clock.get();
674 clock.compareAndSet(c, c + 1);
675 r.setLastAccess(c);
676 }
677
678 private void evict() {
679 while (isFull()) {
680 int ptr = rng.nextInt(tableSize);
681 Entry old = null;
682 int slot = 0;
683 for (int b = evictBatch - 1; b >= 0; b--, ptr++) {
684 if (tableSize <= ptr)
685 ptr = 0;
686 for (Entry e = table.get(ptr); e != null; e = e.next) {
687 if (e.dead)
688 continue;
689 if (old == null || e.ref.getLastAccess() < old.ref
690 .getLastAccess()) {
691 old = e;
692 slot = ptr;
693 }
694 }
695 }
696 if (old != null) {
697 old.kill();
698 gc();
699 final Entry e1 = table.get(slot);
700 table.compareAndSet(slot, e1, clean(e1));
701 }
702 }
703 }
704
705
706
707
708
709
710
711
712
713
714
715 private void removeAll() {
716 for (int s = 0; s < tableSize; s++) {
717 Entry e1;
718 do {
719 e1 = table.get(s);
720 for (Entry e = e1; e != null; e = e.next)
721 e.kill();
722 } while (!table.compareAndSet(s, e1, null));
723 }
724 gc();
725 }
726
727
728
729
730
731
732
733
734
735
736
737
738 private void removeAll(PackFile pack) {
739 for (int s = 0; s < tableSize; s++) {
740 final Entry e1 = table.get(s);
741 boolean hasDead = false;
742 for (Entry e = e1; e != null; e = e.next) {
743 if (e.ref.getPack() == pack) {
744 e.kill();
745 hasDead = true;
746 } else if (e.dead)
747 hasDead = true;
748 }
749 if (hasDead)
750 table.compareAndSet(s, e1, clean(e1));
751 }
752 gc();
753 }
754
755 private void gc() {
756 queue.gc();
757 }
758
759 private int slot(PackFile pack, long position) {
760 return (hash(pack.hash, position) >>> 1) % tableSize;
761 }
762
763 private Lock lock(PackFile pack, long position) {
764 return locks[(hash(pack.hash, position) >>> 1) % locks.length];
765 }
766
767 private static Entry clean(Entry top) {
768 while (top != null && top.dead) {
769 top.ref.kill();
770 top = top.next;
771 }
772 if (top == null)
773 return null;
774 final Entry n = clean(top.next);
775 return n == top.next ? top : new Entry(n, top.ref);
776 }
777
778 private static class Entry {
779
780 final Entry next;
781
782
783 final PageRef<ByteWindow> ref;
784
785
786
787
788
789
790
791
792 volatile boolean dead;
793
794 Entry(Entry n, PageRef<ByteWindow> r) {
795 next = n;
796 ref = r;
797 }
798
799 final void kill() {
800 dead = true;
801 ref.kill();
802 }
803 }
804
805 private static interface PageRef<T> {
806
807
808
809
810
811
812
813
814 T get();
815
816
817
818
819
820
821
822 boolean kill();
823
824
825
826
827
828
829 PackFile getPack();
830
831
832
833
834
835
836 long getPosition();
837
838
839
840
841
842
843 int getSize();
844
845
846
847
848
849
850 long getLastAccess();
851
852
853
854
855
856
857
858 void setLastAccess(long time);
859
860
861
862
863
864 boolean isStrongRef();
865 }
866
867
868 private static class SoftRef extends SoftReference<ByteWindow>
869 implements PageRef<ByteWindow> {
870 private final PackFile pack;
871
872 private final long position;
873
874 private final int size;
875
876 private long lastAccess;
877
878 protected SoftRef(final PackFile pack, final long position,
879 final ByteWindow v, final SoftCleanupQueue queue) {
880 super(v, queue);
881 this.pack = pack;
882 this.position = position;
883 this.size = v.size();
884 }
885
886 @Override
887 public PackFile getPack() {
888 return pack;
889 }
890
891 @Override
892 public long getPosition() {
893 return position;
894 }
895
896 @Override
897 public int getSize() {
898 return size;
899 }
900
901 @Override
902 public long getLastAccess() {
903 return lastAccess;
904 }
905
906 @Override
907 public void setLastAccess(long time) {
908 this.lastAccess = time;
909 }
910
911 @Override
912 public boolean kill() {
913 return enqueue();
914 }
915
916 @Override
917 public boolean isStrongRef() {
918 return false;
919 }
920 }
921
922
923 private static class StrongRef implements PageRef<ByteWindow> {
924 private ByteWindow referent;
925
926 private final PackFile pack;
927
928 private final long position;
929
930 private final int size;
931
932 private long lastAccess;
933
934 private CleanupQueue queue;
935
936 protected StrongRef(final PackFile pack, final long position,
937 final ByteWindow v, final CleanupQueue queue) {
938 this.pack = pack;
939 this.position = position;
940 this.referent = v;
941 this.size = v.size();
942 this.queue = queue;
943 }
944
945 @Override
946 public PackFile getPack() {
947 return pack;
948 }
949
950 @Override
951 public long getPosition() {
952 return position;
953 }
954
955 @Override
956 public int getSize() {
957 return size;
958 }
959
960 @Override
961 public long getLastAccess() {
962 return lastAccess;
963 }
964
965 @Override
966 public void setLastAccess(long time) {
967 this.lastAccess = time;
968 }
969
970 @Override
971 public ByteWindow get() {
972 return referent;
973 }
974
975 @Override
976 public boolean kill() {
977 if (referent == null) {
978 return false;
979 }
980 referent = null;
981 return queue.enqueue(this);
982 }
983
984 @Override
985 public boolean isStrongRef() {
986 return true;
987 }
988 }
989
990 private static interface CleanupQueue {
991 boolean enqueue(PageRef<ByteWindow> r);
992 void gc();
993 }
994
995 private static class SoftCleanupQueue extends ReferenceQueue<ByteWindow>
996 implements CleanupQueue {
997 private final WindowCache wc;
998
999 SoftCleanupQueue(WindowCache cache) {
1000 this.wc = cache;
1001 }
1002
1003 @Override
1004 public boolean enqueue(PageRef<ByteWindow> r) {
1005
1006
1007 return false;
1008 }
1009
1010 @Override
1011 public void gc() {
1012 SoftRef r;
1013 while ((r = (SoftRef) poll()) != null) {
1014 wc.clear(r);
1015
1016 final int s = wc.slot(r.getPack(), r.getPosition());
1017 final Entry e1 = wc.table.get(s);
1018 for (Entry n = e1; n != null; n = n.next) {
1019 if (n.ref == r) {
1020 n.dead = true;
1021 wc.table.compareAndSet(s, e1, clean(e1));
1022 break;
1023 }
1024 }
1025 }
1026 }
1027 }
1028
1029 private static class StrongCleanupQueue implements CleanupQueue {
1030 private final WindowCache wc;
1031
1032 private final ConcurrentLinkedQueue<PageRef<ByteWindow>> queue = new ConcurrentLinkedQueue<>();
1033
1034 StrongCleanupQueue(WindowCache wc) {
1035 this.wc = wc;
1036 }
1037
1038 @Override
1039 public boolean enqueue(PageRef<ByteWindow> r) {
1040 if (queue.contains(r)) {
1041 return false;
1042 }
1043 return queue.add(r);
1044 }
1045
1046 @Override
1047 public void gc() {
1048 PageRef<ByteWindow> r;
1049 while ((r = queue.poll()) != null) {
1050 wc.clear(r);
1051
1052 final int s = wc.slot(r.getPack(), r.getPosition());
1053 final Entry e1 = wc.table.get(s);
1054 for (Entry n = e1; n != null; n = n.next) {
1055 if (n.ref == r) {
1056 n.dead = true;
1057 wc.table.compareAndSet(s, e1, clean(e1));
1058 break;
1059 }
1060 }
1061 }
1062 }
1063 }
1064
1065 private static final class Lock {
1066
1067 }
1068 }