SymbolsCache { public BuildMetadataEntry getEntry(@NotNull final String key, @NotNull final MetadataSource metadataSource) { Boolean missedSymbol = myMissedSymbols.getIfPresent(key); if (missedSymbol != null && missedSymbol) { LOG.debug("Symbol server does not host the symbol. Missed symbols cache contains key: " + key); return null; } try { Long buildId = myKeyToBuildIdMap.get(key); if (buildId == null) { LOG.debug("Searching buildId by key. Key: " + key); buildId = metadataSource.getBuildIdByEntryKey(key); if (buildId == null) { LOG.debug("Could not found buildId by key. Key: " + key); myMissedSymbols.put(key, true); return null; } else { myKeyToBuildIdMap.put(key, buildId); } } LOG.debug("Key was found in keyToBuildMap. Key: " + key + ", BuildId: " + buildId); final AtomicBoolean shouldUpdateMap = new AtomicBoolean(false); final Long lambdaBuildId = buildId; final Optional<Map<String, BuildMetadataEntry>> buildEntries = myCachedBuilds.get(buildId, () -> { final List<BuildMetadataEntry> entries = metadataSource.getEntriesByBuildId(lambdaBuildId); if (entries.isEmpty()) { return Optional.empty(); } shouldUpdateMap.set(true); final HashMap<String, BuildMetadataEntry> result = new HashMap<String, BuildMetadataEntry>(); for (BuildMetadataEntry entry: entries) { result.put(entry.getKey(), entry); } return Optional.of(result); }); if (buildEntries.isPresent()) { LOG.debug("Build entries was found in cache. BuildId: " + buildId); final Map<String, BuildMetadataEntry> buildEntriesMap = buildEntries.get(); if (shouldUpdateMap.get()) { for (String entryKey: buildEntriesMap.keySet()) { myKeyToBuildIdMap.put(entryKey, lambdaBuildId); myMissedSymbols.invalidate(entryKey); } } final BuildMetadataEntry metadata = buildEntriesMap.get(key); if (metadata != null) { if (LOG.isDebugEnabled()) { LOG.debug("Key was found in cache. Key: " + key + ", Value: " + metadata); } return metadata; } } LOG.debug("Key was found in keyToBuildIdMap but there was no such build in cache. Removing key from keyToBuildIdMap. Key: " + key + ", BuildId: " + buildId); myKeyToBuildIdMap.remove(key); } catch (ExecutionException | InterruptedException | TimeoutException e) { LOG.error("Exception occured during metadata loading", e); } return null; } SymbolsCache(@NotNull final EventDispatcher<BuildServerListener> events); BuildMetadataEntry getEntry(@NotNull final String key,
                                     @NotNull final MetadataSource metadataSource); void invalidate(long buildId);  }
@Test public void shouldNotReadCachedEntryAgainFromSource() throws TimeoutException, InterruptedException { SymbolsCache symbolsCache = new SymbolsCache(myFixture.getEventDispatcher()); Mockery m = new Mockery(); BuildMetadataEntry entry = m.mock(BuildMetadataEntry.class); List<BuildMetadataEntry> entryList = Collections.singletonList(entry); MetadataSource metadataSource = m.mock(MetadataSource.class); String key = "key"; Long buildId = 123L; m.checking(new Expectations(){{ oneOf(entry).getBuildId(); will(returnValue(buildId)); one(entry).getKey(); will(returnValue(key)); one(metadataSource).getBuildIdByEntryKey(key); will(returnValue(buildId)); one(metadataSource).getEntriesByBuildId(buildId); will(returnValue(entryList)); }}); BuildMetadataEntry cacheEntry1 = symbolsCache.getEntry(key, metadataSource); BuildMetadataEntry cacheEntry2 = symbolsCache.getEntry(key, metadataSource); Assert.assertNotNull(cacheEntry1); Assert.assertEquals(cacheEntry1, cacheEntry2); Assert.assertEquals(cacheEntry2, entry); }
@Test public void shouldSearchOnceForNonExistentKeys() throws TimeoutException, InterruptedException { SymbolsCache symbolsCache = new SymbolsCache(myFixture.getEventDispatcher()); Mockery m = new Mockery(); MetadataSource metadataSource = m.mock(MetadataSource.class); String key = "key"; Long buildId = null; m.checking(new Expectations(){{ one(metadataSource).getBuildIdByEntryKey(key); will(returnValue(buildId)); }}); BuildMetadataEntry cacheEntry1 = symbolsCache.getEntry(key, metadataSource); BuildMetadataEntry cacheEntry2 = symbolsCache.getEntry(key, metadataSource); Assert.assertNull(cacheEntry1); Assert.assertNull(cacheEntry2); }
@Test public void shouldReadWholeBuild() throws TimeoutException, InterruptedException { SymbolsCache symbolsCache = new SymbolsCache(myFixture.getEventDispatcher()); Mockery m = new Mockery(); String key = "key"; BuildMetadataEntry entry = m.mock(BuildMetadataEntry.class, key); String key2 = "key2"; BuildMetadataEntry entry2 = m.mock(BuildMetadataEntry.class, key2); List<BuildMetadataEntry> entryList = Arrays.asList(entry, entry2); MetadataSource metadataSource = m.mock(MetadataSource.class); Long buildId = 123L; m.checking(new Expectations(){{ one(entry).getBuildId(); will(returnValue(buildId)); one(entry).getKey(); will(returnValue(key)); one(entry2).getKey(); will(returnValue(key2)); one(entry2).getBuildId(); will(returnValue(buildId)); one(metadataSource).getBuildIdByEntryKey(key); will(returnValue(buildId)); one(metadataSource).getEntriesByBuildId(buildId); will(returnValue(entryList)); }}); symbolsCache.getEntry(key, metadataSource); BuildMetadataEntry cacheEntry2 = symbolsCache.getEntry(key2, metadataSource); Assert.assertEquals(cacheEntry2, entry2); }