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