1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.lib;
12
13 import java.io.File;
14 import java.io.IOException;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Map;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.ScheduledThreadPoolExecutor;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jgit.annotations.NonNull;
24 import org.eclipse.jgit.errors.RepositoryNotFoundException;
25 import org.eclipse.jgit.internal.storage.file.FileRepository;
26 import org.eclipse.jgit.lib.internal.WorkQueue;
27 import org.eclipse.jgit.util.FS;
28 import org.eclipse.jgit.util.IO;
29 import org.eclipse.jgit.util.RawParseUtils;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33
34
35
36 public class RepositoryCache {
37 private static final Logger LOG = LoggerFactory
38 .getLogger(RepositoryCache.class);
39
40 private static final RepositoryCache cache = new RepositoryCache();
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public static Repository open(Key location) throws IOException,
60 RepositoryNotFoundException {
61 return open(location, true);
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 public static Repository open(Key location, boolean mustExist)
87 throws IOException {
88 return cache.openRepository(location, mustExist);
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 public static void register(Repository db) {
107 if (db.getDirectory() != null) {
108 FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
109 cache.registerRepository(key, db);
110 }
111 }
112
113
114
115
116
117
118
119
120
121
122 public static void close(@NonNull Repository db) {
123 if (db.getDirectory() != null) {
124 FileKey key = FileKey.exact(db.getDirectory(), db.getFS());
125 cache.unregisterAndCloseRepository(key);
126 }
127 }
128
129
130
131
132
133
134
135
136
137
138
139
140
141 public static void unregister(Repository db) {
142 if (db.getDirectory() != null) {
143 unregister(FileKey.exact(db.getDirectory(), db.getFS()));
144 }
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159 public static void unregister(Key location) {
160 cache.unregisterRepository(location);
161 }
162
163
164
165
166
167
168
169 public static Collection<Key> getRegisteredKeys() {
170 return cache.getKeys();
171 }
172
173 static boolean isCached(@NonNull Repository repo) {
174 File gitDir = repo.getDirectory();
175 if (gitDir == null) {
176 return false;
177 }
178 FileKey key = new FileKey(gitDir, repo.getFS());
179 return cache.cacheMap.get(key) == repo;
180 }
181
182
183
184
185 public static void clear() {
186 cache.clearAll();
187 }
188
189 static void clearExpired() {
190 cache.clearAllExpired();
191 }
192
193 static void reconfigure(RepositoryCacheConfig repositoryCacheConfig) {
194 cache.configureEviction(repositoryCacheConfig);
195 }
196
197 private final Map<Key, Repository> cacheMap;
198
199 private final Lock[] openLocks;
200
201 private ScheduledFuture<?> cleanupTask;
202
203 private volatile long expireAfter;
204
205 private final Object schedulerLock = new Lock();
206
207 private RepositoryCache() {
208 cacheMap = new ConcurrentHashMap<>();
209 openLocks = new Lock[4];
210 for (int i = 0; i < openLocks.length; i++) {
211 openLocks[i] = new Lock();
212 }
213 configureEviction(new RepositoryCacheConfig());
214 }
215
216 private void configureEviction(
217 RepositoryCacheConfig repositoryCacheConfig) {
218 expireAfter = repositoryCacheConfig.getExpireAfter();
219 ScheduledThreadPoolExecutor scheduler = WorkQueue.getExecutor();
220 synchronized (schedulerLock) {
221 if (cleanupTask != null) {
222 cleanupTask.cancel(false);
223 }
224 long delay = repositoryCacheConfig.getCleanupDelay();
225 if (delay == RepositoryCacheConfig.NO_CLEANUP) {
226 return;
227 }
228 cleanupTask = scheduler.scheduleWithFixedDelay(() -> {
229 try {
230 cache.clearAllExpired();
231 } catch (Throwable e) {
232 LOG.error(e.getMessage(), e);
233 }
234 }, delay, delay, TimeUnit.MILLISECONDS);
235 }
236 }
237
238 private Repository openRepository(final Key location,
239 final boolean mustExist) throws IOException {
240 Repository db = cacheMap.get(location);
241 if (db == null) {
242 synchronized (lockFor(location)) {
243 db = cacheMap.get(location);
244 if (db == null) {
245 db = location.open(mustExist);
246 cacheMap.put(location, db);
247 } else {
248 db.incrementOpen();
249 }
250 }
251 } else {
252 db.incrementOpen();
253 }
254 return db;
255 }
256
257 private void registerRepository(Key location, Repository db) {
258 try (Repository oldDb = cacheMap.put(location, db)) {
259
260 }
261 }
262
263 private Repository unregisterRepository(Key location) {
264 return cacheMap.remove(location);
265 }
266
267 private boolean isExpired(Repository db) {
268 return db != null && db.useCnt.get() <= 0
269 && (System.currentTimeMillis() - db.closedAt.get() > expireAfter);
270 }
271
272 private void unregisterAndCloseRepository(Key location) {
273 synchronized (lockFor(location)) {
274 Repository oldDb = unregisterRepository(location);
275 if (oldDb != null) {
276 oldDb.doClose();
277 }
278 }
279 }
280
281 private Collection<Key> getKeys() {
282 return new ArrayList<>(cacheMap.keySet());
283 }
284
285 private void clearAllExpired() {
286 for (Repository db : cacheMap.values()) {
287 if (isExpired(db)) {
288 RepositoryCache.close(db);
289 }
290 }
291 }
292
293 private void clearAll() {
294 for (Key k : cacheMap.keySet()) {
295 unregisterAndCloseRepository(k);
296 }
297 }
298
299 private Lock lockFor(Key location) {
300 return openLocks[(location.hashCode() >>> 1) % openLocks.length];
301 }
302
303 private static class Lock {
304
305 }
306
307
308
309
310
311
312
313
314 public static interface Key {
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333 Repository open(boolean mustExist) throws IOException,
334 RepositoryNotFoundException;
335 }
336
337
338 public static class FileKey implements Key {
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353 public static FileKey exact(File directory, FS fs) {
354 return new FileKey(directory, fs);
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376 public static FileKey lenient(File directory, FS fs) {
377 final File gitdir = resolve(directory, fs);
378 return new FileKey(gitdir != null ? gitdir : directory, fs);
379 }
380
381 private final File path;
382 private final FS fs;
383
384
385
386
387
388
389
390
391 protected FileKey(File directory, FS fs) {
392 path = canonical(directory);
393 this.fs = fs;
394 }
395
396 private static File canonical(File path) {
397 try {
398 return path.getCanonicalFile();
399 } catch (IOException e) {
400 return path.getAbsoluteFile();
401 }
402 }
403
404
405 public final File getFile() {
406 return path;
407 }
408
409 @Override
410 public Repository open(boolean mustExist) throws IOException {
411 if (mustExist && !isGitRepository(path, fs))
412 throw new RepositoryNotFoundException(path);
413 return new FileRepository(path);
414 }
415
416 @Override
417 public int hashCode() {
418 return path.hashCode();
419 }
420
421 @Override
422 public boolean equals(Object o) {
423 return o instanceof FileKey && path.equals(((FileKey) o).path);
424 }
425
426 @Override
427 public String toString() {
428 return path.toString();
429 }
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446 public static boolean isGitRepository(File dir, FS fs) {
447 return fs.resolve(dir, Constants.OBJECTS).exists()
448 && fs.resolve(dir, "refs").exists()
449 && (fs.resolve(dir, Constants.REFTABLE).exists()
450 || isValidHead(new File(dir, Constants.HEAD)));
451 }
452
453 private static boolean isValidHead(File head) {
454 final String ref = readFirstLine(head);
455 return ref != null
456 && (ref.startsWith("ref: refs/") || ObjectId.isId(ref));
457 }
458
459 private static String readFirstLine(File head) {
460 try {
461 final byte[] buf = IO.readFully(head, 4096);
462 int n = buf.length;
463 if (n == 0)
464 return null;
465 if (buf[n - 1] == '\n')
466 n--;
467 return RawParseUtils.decode(buf, 0, n);
468 } catch (IOException e) {
469 return null;
470 }
471 }
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492 public static File resolve(File directory, FS fs) {
493 if (isGitRepository(directory, fs))
494 return directory;
495 if (isGitRepository(new File(directory, Constants.DOT_GIT), fs))
496 return new File(directory, Constants.DOT_GIT);
497
498 final String name = directory.getName();
499 final File parent = directory.getParentFile();
500 if (isGitRepository(new File(parent, name + Constants.DOT_GIT_EXT), fs))
501 return new File(parent, name + Constants.DOT_GIT_EXT);
502 return null;
503 }
504 }
505 }