Skip to content

Commit e7d9d6d

Browse files
committed
delta-based index cache: store md5 hash in timestamp map to identify files instead of full file path
1 parent 8d2ba77 commit e7d9d6d

File tree

1 file changed

+117
-57
lines changed

1 file changed

+117
-57
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java

Lines changed: 117 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
import java.util.SortedMap;
3131
import java.util.TreeMap;
3232
import java.util.concurrent.ConcurrentHashMap;
33+
import java.util.concurrent.ConcurrentMap;
3334
import java.util.stream.Collectors;
3435

36+
import org.apache.commons.codec.digest.DigestUtils;
3537
import org.apache.commons.lang3.tuple.Pair;
3638
import org.eclipse.lsp4j.Location;
3739
import org.slf4j.Logger;
@@ -66,7 +68,7 @@
6668
public class IndexCacheOnDiscDeltaBased implements IndexCache {
6769

6870
private final File cacheDirectory;
69-
private final Map<IndexCacheKey, Map<String, Long>> timestamps;
71+
private final Map<IndexCacheKey, ConcurrentMap<InternalFileIdentifier, Long>> timestamps;
7072

7173
private static final Logger log = LoggerFactory.getLogger(IndexCacheOnDiscDeltaBased.class);
7274

@@ -106,7 +108,8 @@ public <T extends IndexCacheable> void store(IndexCacheKey cacheKey, String[] fi
106108
persist(cacheKey, new DeltaSnapshot<T>(store), false);
107109

108110
// update local timestamp cache
109-
ConcurrentHashMap<String, Long> timestampMap = new ConcurrentHashMap<>(timestampedFiles);
111+
ConcurrentMap<InternalFileIdentifier, Long> timestampMap = timestampedFiles.entrySet().stream()
112+
.collect(Collectors.toConcurrentMap(e -> InternalFileIdentifier.fromPath(e.getKey()), e -> e.getValue()));
110113
this.timestamps.put(cacheKey, timestampMap);
111114
}
112115

@@ -143,7 +146,9 @@ public <T extends IndexCacheable> Pair<T[], Multimap<String, String>> retrieve(I
143146
}
144147

145148
// update local timestamp cache
146-
this.timestamps.put(cacheKey, new ConcurrentHashMap<>(timestampedFiles));
149+
ConcurrentMap<InternalFileIdentifier, Long> timestampMap = timestampedFiles.entrySet().stream()
150+
.collect(Collectors.toConcurrentMap(e -> InternalFileIdentifier.fromPath(e.getKey()), e -> e.getValue()));
151+
this.timestamps.put(cacheKey, timestampMap);
147152

148153
return Pair.of(
149154
(T[]) symbols.toArray((T[]) Array.newInstance(type, symbols.size())),
@@ -168,10 +173,10 @@ public <T extends IndexCacheable> void removeFiles(IndexCacheKey cacheKey, Strin
168173
persist(cacheKey, new DeltaDelete<T>(files), true);
169174

170175
// update local timestamp cache
171-
Map<String, Long> timestampsMap = this.timestamps.get(cacheKey);
176+
Map<InternalFileIdentifier, Long> timestampsMap = this.timestamps.get(cacheKey);
172177
if (timestampsMap != null) {
173178
for (String file : files) {
174-
timestampsMap.remove(file);
179+
timestampsMap.remove(InternalFileIdentifier.fromPath(file));
175180
}
176181
}
177182
}
@@ -205,8 +210,8 @@ public <T extends IndexCacheable> void update(IndexCacheKey cacheKey, String fil
205210
persist(cacheKey, new DeltaUpdate<T>(deltaStore), true);
206211

207212
// update local timestamp cache
208-
Map<String, Long> timestampsMap = this.timestamps.computeIfAbsent(cacheKey, (s) -> new ConcurrentHashMap<>());
209-
timestampsMap.put(file, lastModified);
213+
Map<InternalFileIdentifier, Long> timestampsMap = this.timestamps.computeIfAbsent(cacheKey, (s) -> new ConcurrentHashMap<>());
214+
timestampsMap.put(InternalFileIdentifier.fromPath(file), lastModified);
210215
}
211216

212217
@Override
@@ -229,17 +234,19 @@ public <T extends IndexCacheable> void update(IndexCacheKey cacheKey, String[] f
229234
persist(cacheKey, new DeltaUpdate<T>(deltaStore), true);
230235

231236
// update local timestamp cache
232-
Map<String, Long> timestampsMap = this.timestamps.computeIfAbsent(cacheKey, (s) -> new ConcurrentHashMap<>());
237+
Map<InternalFileIdentifier, Long> timestampsMap = this.timestamps.computeIfAbsent(cacheKey, (s) -> new ConcurrentHashMap<>());
233238
for (int i = 0; i < files.length; i++) {
234-
timestampsMap.put(files[i], lastModified[i]);
239+
timestampsMap.put(InternalFileIdentifier.fromPath(files[i]), lastModified[i]);
235240
}
236241
}
237242

238243
@Override
239244
public long getModificationTimestamp(IndexCacheKey cacheKey, String file) {
240-
Map<String, Long> timestampsMap = this.timestamps.get(cacheKey);
245+
InternalFileIdentifier fileID = InternalFileIdentifier.fromPath(file);
246+
247+
Map<InternalFileIdentifier, Long> timestampsMap = this.timestamps.get(cacheKey);
241248
if (timestampsMap != null) {
242-
Long result = timestampsMap.get(file);
249+
Long result = timestampsMap.get(fileID);
243250
if (result != null) {
244251
return result;
245252
}
@@ -335,12 +342,106 @@ public static Gson createGson() {
335342
.create();
336343
}
337344

345+
346+
/**
347+
* just keep a md5 hash internally for identifying files to save memory
348+
*/
349+
private static class InternalFileIdentifier {
350+
351+
public static InternalFileIdentifier fromPath(String fileName) {
352+
byte[] id = DigestUtils.md5(fileName);
353+
return new InternalFileIdentifier( id);
354+
}
355+
356+
private byte[] id;
357+
358+
private InternalFileIdentifier(byte[] id) {
359+
this.id = id;
360+
}
361+
362+
@Override
363+
public int hashCode() {
364+
return Arrays.hashCode(id);
365+
}
366+
367+
@Override
368+
public boolean equals(Object obj) {
369+
if (this == obj)
370+
return true;
371+
if (obj == null)
372+
return false;
373+
if (getClass() != obj.getClass())
374+
return false;
375+
InternalFileIdentifier other = (InternalFileIdentifier) obj;
376+
return Arrays.equals(id, other.id);
377+
}
378+
379+
}
380+
381+
382+
383+
384+
/**
385+
* internal storage structure
386+
*/
387+
private static class IndexCacheStore<T extends IndexCacheable> {
388+
389+
@SuppressWarnings("unused")
390+
private final String elementType;
391+
392+
private final SortedMap<String, Long> timestampedFiles;
393+
private final List<T> elements;
394+
private final Map<String, Collection<String>> dependencies;
395+
396+
public IndexCacheStore(SortedMap<String, Long> timestampedFiles, List<T> elements, Map<String, Collection<String>> dependencies, Class<T> elementType) {
397+
this.timestampedFiles = timestampedFiles;
398+
this.elements = elements;
399+
this.dependencies = dependencies;
400+
this.elementType = elementType.getName();
401+
}
402+
403+
public Map<String, Collection<String>> getDependencies() {
404+
return dependencies;
405+
}
406+
407+
public List<T> getSymbols() {
408+
return elements;
409+
}
410+
411+
public SortedMap<String, Long> getTimestampedFiles() {
412+
return timestampedFiles;
413+
}
414+
415+
}
416+
417+
418+
//
419+
//
420+
// internal delta-based storage structure: snapshots, updates, and deletions
421+
//
422+
//
423+
424+
338425
private static record DeltaStorage<T extends IndexCacheable> (DeltaElement<T> storedElement) {}
339426

340427
private static interface DeltaElement<T extends IndexCacheable> {
341428
public IndexCacheStore<T> apply(IndexCacheStore<T> store);
342429
}
343430

431+
private static class DeltaSnapshot<T extends IndexCacheable> implements DeltaElement<T> {
432+
433+
private IndexCacheStore<T> store;
434+
435+
public DeltaSnapshot(IndexCacheStore<T> store) {
436+
this.store = store;
437+
}
438+
439+
@Override
440+
public IndexCacheStore<T> apply(IndexCacheStore<T> store) {
441+
return this.store;
442+
}
443+
}
444+
344445
private static class DeltaDelete<T extends IndexCacheable> implements DeltaElement<T> {
345446

346447
private final String[] files;
@@ -433,53 +534,12 @@ public IndexCacheStore<T> apply(IndexCacheStore<T> store) {
433534

434535
}
435536

436-
private static class DeltaSnapshot<T extends IndexCacheable> implements DeltaElement<T> {
437-
438-
private IndexCacheStore<T> store;
439-
440-
public DeltaSnapshot(IndexCacheStore<T> store) {
441-
this.store = store;
442-
}
443-
444-
@Override
445-
public IndexCacheStore<T> apply(IndexCacheStore<T> store) {
446-
return this.store;
447-
}
448-
}
449-
450-
451-
/**
452-
* internal storage structure
453-
*/
454-
private static class IndexCacheStore<T extends IndexCacheable> {
455-
456-
@SuppressWarnings("unused")
457-
private final String elementType;
458537

459-
private final SortedMap<String, Long> timestampedFiles;
460-
private final List<T> elements;
461-
private final Map<String, Collection<String>> dependencies;
462-
463-
public IndexCacheStore(SortedMap<String, Long> timestampedFiles, List<T> elements, Map<String, Collection<String>> dependencies, Class<T> elementType) {
464-
this.timestampedFiles = timestampedFiles;
465-
this.elements = elements;
466-
this.dependencies = dependencies;
467-
this.elementType = elementType.getName();
468-
}
469-
470-
public Map<String, Collection<String>> getDependencies() {
471-
return dependencies;
472-
}
473-
474-
public List<T> getSymbols() {
475-
return elements;
476-
}
477-
478-
public SortedMap<String, Long> getTimestampedFiles() {
479-
return timestampedFiles;
480-
}
481-
482-
}
538+
//
539+
//
540+
// GSON serialize / deserialize adapters for the various types involved here that have special needs around JSON
541+
//
542+
//
483543

484544
private static class IndexCacheStoreAdapter implements JsonDeserializer<IndexCacheStore<?>> {
485545

0 commit comments

Comments
 (0)