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