Skip to content

Commit f0d57a0

Browse files
author
Vladimir Kotal
authored
FileHistoryCache#get() should not modify history cache (#3828)
* add test for get() vs. store() * do not modify history cache in FileHistoryCache#get()
1 parent 3434df0 commit f0d57a0

File tree

7 files changed

+71
-108
lines changed

7 files changed

+71
-108
lines changed

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Configuration.java

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,6 @@ public final class Configuration {
107107
* Should the history log be cached?
108108
*/
109109
private boolean historyCache;
110-
/**
111-
* The maximum time in milliseconds {@code HistoryCache.get()} can take
112-
* before its result is cached.
113-
*/
114-
private int historyCacheTime;
115110
/**
116111
* flag to generate history. This is bigger hammer than @{code historyCache}
117112
* above. If set to false, no history query will be ever made and the webapp
@@ -543,7 +538,6 @@ public Configuration() {
543538
setGroupsCollapseThreshold(4);
544539
setHandleHistoryOfRenamedFiles(false);
545540
setHistoryCache(true);
546-
setHistoryCacheTime(30);
547541
setHistoryEnabled(true);
548542
setHitsPerPage(25);
549543
setIgnoredNames(new IgnoredNames());
@@ -807,28 +801,6 @@ public void setHistoryCache(boolean historyCache) {
807801
this.historyCache = historyCache;
808802
}
809803

810-
/**
811-
* How long can a history request take before it's cached? If the time is
812-
* exceeded, the result is cached. This setting only affects
813-
* {@code FileHistoryCache}.
814-
*
815-
* @return the maximum time in milliseconds a history request can take
816-
* before it's cached
817-
*/
818-
public int getHistoryCacheTime() {
819-
return historyCacheTime;
820-
}
821-
822-
/**
823-
* Set the maximum time a history request can take before it's cached. This
824-
* setting is only respected if {@code FileHistoryCache} is used.
825-
*
826-
* @param historyCacheTime maximum time in milliseconds
827-
*/
828-
public void setHistoryCacheTime(int historyCacheTime) {
829-
this.historyCacheTime = historyCacheTime;
830-
}
831-
832804
public boolean isFetchHistoryWhenNotInCache() {
833805
return fetchHistoryWhenNotInCache;
834806
}

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -656,25 +656,6 @@ public Set<String> getCtagsLanguages() {
656656
return Collections.unmodifiableSet(ctagsLanguages);
657657
}
658658

659-
/**
660-
* Get the max time a SCM operation may use to avoid being cached.
661-
*
662-
* @return the maximum time in milliseconds
663-
*/
664-
public int getHistoryReaderTimeLimit() {
665-
return syncReadConfiguration(Configuration::getHistoryCacheTime);
666-
}
667-
668-
/**
669-
* Specify the maximum time a SCM operation should take before it will be
670-
* cached (in ms).
671-
*
672-
* @param historyCacheTime the max time in ms before it is cached
673-
*/
674-
public void setHistoryReaderTimeLimit(int historyCacheTime) {
675-
syncWriteConfiguration(historyCacheTime, Configuration::setHistoryCacheTime);
676-
}
677-
678659
/**
679660
* Is history cache currently enabled?
680661
*

opengrok-indexer/src/main/java/org/opengrok/indexer/history/FileHistoryCache.java

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ public void initialize() {
177177
}
178178
}
179179

180+
double getFileHistoryCacheHits() {
181+
return fileHistoryCacheHits.count();
182+
}
183+
180184
@Override
181185
public void optimize() {
182186
// nothing to do
@@ -669,39 +673,14 @@ public History get(File file, Repository repository, boolean withFiles)
669673
}
670674

671675
final History history;
672-
long time;
673676
try {
674-
time = System.currentTimeMillis();
675677
history = repository.getHistory(file);
676-
time = System.currentTimeMillis() - time;
677678
} catch (UnsupportedOperationException e) {
678679
// In this case, we've found a file for which the SCM has no history
679680
// An example is a non-SCCS file somewhere in an SCCS-controlled workspace.
680681
return null;
681682
}
682683

683-
// Don't cache history-information for directories, since the
684-
// history information on the directory may change if a file in
685-
// a sub-directory change. This will cause us to present a stale
686-
// history log until the current directory is updated and
687-
// invalidates the cache entry.
688-
if (!file.isDirectory()) {
689-
// Either the cache is stale or retrieving the history took too long, cache it!
690-
if (cacheFile.exists()) {
691-
if (LOGGER.isLoggable(Level.FINEST)) {
692-
LOGGER.log(Level.FINEST, "refreshing history for ''{0}'': {1}",
693-
new Object[]{file, history.getRevisionList()});
694-
}
695-
storeFile(history, file, repository);
696-
} else if (time > env.getHistoryReaderTimeLimit()) {
697-
if (LOGGER.isLoggable(Level.FINEST)) {
698-
LOGGER.log(Level.FINEST, "getting history for ''{0}'' took longer than {1} ms, caching it: {2}",
699-
new Object[]{file, env.getHistoryReaderTimeLimit(), history.getRevisionList()});
700-
}
701-
storeFile(history, file, repository);
702-
}
703-
}
704-
705684
return history;
706685
}
707686

opengrok-indexer/src/test/java/org/opengrok/indexer/configuration/ConfigMergeTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
*
3232
* @author vkotal
3333
*/
34-
public class ConfigMergeTest {
34+
class ConfigMergeTest {
3535
@Test
36-
public void basicTest() throws Exception {
36+
void basicTest() throws Exception {
3737

3838
String srcRoot = "/foo";
3939
String dataRoot = "/bar";

opengrok-indexer/src/test/java/org/opengrok/indexer/configuration/RuntimeEnvironmentTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,6 @@ public void testCtags() {
215215
"instance ctags should equals 'ctags' or the sys property");
216216
}
217217

218-
@Test
219-
public void testHistoryReaderTimeLimit() {
220-
RuntimeEnvironment instance = RuntimeEnvironment.getInstance();
221-
assertEquals(30, instance.getHistoryReaderTimeLimit());
222-
instance.setHistoryReaderTimeLimit(50);
223-
assertEquals(50, instance.getHistoryReaderTimeLimit());
224-
}
225-
226218
@Test
227219
public void testFetchHistoryWhenNotInCache() {
228220
RuntimeEnvironment instance = RuntimeEnvironment.getInstance();

opengrok-indexer/src/test/java/org/opengrok/indexer/history/FileHistoryCacheTest.java

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import static org.junit.jupiter.api.Assertions.assertEquals;
2828
import static org.junit.jupiter.api.Assertions.assertFalse;
29+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2930
import static org.junit.jupiter.api.Assertions.assertNotNull;
3031
import static org.junit.jupiter.api.Assertions.assertNull;
3132
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -42,6 +43,7 @@
4243
import java.nio.file.Files;
4344
import java.nio.file.Path;
4445
import java.nio.file.Paths;
46+
import java.nio.file.attribute.FileTime;
4547
import java.util.Date;
4648
import java.util.Iterator;
4749
import java.util.LinkedList;
@@ -63,7 +65,6 @@
6365
import org.opengrok.indexer.configuration.IgnoredNames;
6466
import org.opengrok.indexer.configuration.RuntimeEnvironment;
6567
import org.opengrok.indexer.util.IOUtils;
66-
import org.opengrok.indexer.util.TandemPath;
6768
import org.opengrok.indexer.util.TestRepository;
6869

6970
/**
@@ -80,7 +81,6 @@ class FileHistoryCacheTest {
8081
private FileHistoryCache cache;
8182

8283
private boolean savedFetchHistoryWhenNotInCache;
83-
private int savedHistoryReaderTimeLimit;
8484
private boolean savedIsHandleHistoryOfRenamedFiles;
8585
private boolean savedIsTagsEnabled;
8686

@@ -96,7 +96,6 @@ public void setUp() throws Exception {
9696
cache.initialize();
9797

9898
savedFetchHistoryWhenNotInCache = env.isFetchHistoryWhenNotInCache();
99-
savedHistoryReaderTimeLimit = env.getHistoryReaderTimeLimit();
10099
savedIsHandleHistoryOfRenamedFiles = env.isHandleHistoryOfRenamedFiles();
101100
savedIsTagsEnabled = env.isTagsEnabled();
102101
}
@@ -112,7 +111,6 @@ public void tearDown() {
112111
cache = null;
113112

114113
env.setFetchHistoryWhenNotInCache(savedFetchHistoryWhenNotInCache);
115-
env.setHistoryReaderTimeLimit(savedHistoryReaderTimeLimit);
116114
env.setIgnoredNames(new IgnoredNames());
117115
env.setIncludedNames(new Filter());
118116
env.setHandleHistoryOfRenamedFiles(savedIsHandleHistoryOfRenamedFiles);
@@ -156,6 +154,57 @@ private void assertSameEntry(HistoryEntry expected, HistoryEntry actual, boolean
156154
}
157155
}
158156

157+
/**
158+
* {@link FileHistoryCache#get(File, Repository, boolean)} should not disturb history cache
159+
* if run between repository update and reindex.
160+
*/
161+
@EnabledOnOs({OS.LINUX, OS.MAC, OS.SOLARIS, OS.AIX, OS.OTHER})
162+
@EnabledForRepository(MERCURIAL)
163+
@Test
164+
void testStoreTouchGet() throws Exception {
165+
File reposRoot = new File(repositories.getSourceRoot(), "mercurial");
166+
Repository repo = RepositoryFactory.getRepository(reposRoot);
167+
History historyToStore = repo.getHistory(reposRoot);
168+
169+
cache.store(historyToStore, repo);
170+
171+
// This makes sure that the file which contains the latest revision has indeed been created.
172+
assertEquals("9:8b340409b3a8", cache.getLatestCachedRevision(repo));
173+
174+
File file = new File(reposRoot, "main.c");
175+
assertTrue(file.exists());
176+
FileTime fileTimeBeforeImport = Files.getLastModifiedTime(file.toPath());
177+
History historyBeforeImport = cache.get(file, repo, false);
178+
179+
MercurialRepositoryTest.runHgCommand(reposRoot, "import",
180+
Paths.get(getClass().getResource("/history/hg-export.txt").toURI()).toString());
181+
FileTime fileTimeAfterImport = Files.getLastModifiedTime(file.toPath());
182+
assertTrue(fileTimeBeforeImport.compareTo(fileTimeAfterImport) < 0);
183+
184+
// This get() call basically mimics a request through the UI or API.
185+
History historyAfterImport = cache.get(file, repo, false);
186+
assertNotNull(historyAfterImport);
187+
assertNotEquals(historyBeforeImport, historyAfterImport);
188+
189+
// Simulates reindex, or at least its first part when history cache is updated.
190+
repo.createCache(cache, cache.getLatestCachedRevision(repo));
191+
192+
// This makes sure that the file which contains the latest revision has indeed been created.
193+
assertEquals("11:bbb3ce75e1b8", cache.getLatestCachedRevision(repo));
194+
195+
/*
196+
* The history should not be disturbed.
197+
* Make sure that get() retrieved the history from cache. Mocking/spying static methods
198+
* (FileHistoryCache#readCache() in this case) is tricky so use the cache hits metric.
199+
*/
200+
double cacheHitsBeforeGet = cache.getFileHistoryCacheHits();
201+
History historyAfterReindex = cache.get(file, repo, false);
202+
double cacheHitsAfterGet = cache.getFileHistoryCacheHits();
203+
assertNotNull(historyAfterReindex);
204+
assertEquals(historyAfterImport, historyAfterReindex);
205+
assertEquals(1, cacheHitsAfterGet - cacheHitsBeforeGet);
206+
}
207+
159208
/**
160209
* Basic tests for the {@code store()} method on cache with disabled
161210
* handling of renamed files.
@@ -838,8 +887,7 @@ void testRenamedFile() throws Exception {
838887
updatedHistory.getHistoryEntries(), false);
839888
}
840889

841-
private void checkNoHistoryFetchRepo(String reponame, String filename,
842-
boolean hasHistory, boolean historyFileExists) throws Exception {
890+
private void checkNoHistoryFetchRepo(String reponame, String filename, boolean hasHistory) throws Exception {
843891

844892
File reposRoot = new File(repositories.getSourceRoot(), reponame);
845893
Repository repo = RepositoryFactory.getRepository(reposRoot);
@@ -853,13 +901,6 @@ private void checkNoHistoryFetchRepo(String reponame, String filename,
853901
// in history cache.
854902
History retrievedHistory = cache.get(repoFile, repo, true);
855903
assertEquals(hasHistory, retrievedHistory != null);
856-
857-
// The file in history cache should not exist since
858-
// FetchHistoryWhenNotInCache is set to false.
859-
File dataRoot = new File(repositories.getDataRoot(),
860-
"historycache" + File.separatorChar + reponame);
861-
File fileHistory = new File(dataRoot, TandemPath.join(filename, ".gz"));
862-
assertEquals(historyFileExists, fileHistory.exists());
863904
}
864905

865906
/*
@@ -871,19 +912,13 @@ void testNoHistoryFetch() throws Exception {
871912
// Do not create history cache for files which do not have it cached.
872913
env.setFetchHistoryWhenNotInCache(false);
873914

874-
// Make cache.get() predictable. Normally when the retrieval of
875-
// history of given file is faster than the limit, the history of this
876-
// file is not stored. For the sake of this test we want the history
877-
// to be always stored.
878-
env.setHistoryReaderTimeLimit(0);
879-
880915
// Pretend we are done with first phase of indexing.
881916
cache.setHistoryIndexDone();
882917

883918
// First try repo with ability to fetch history for directories.
884-
checkNoHistoryFetchRepo("mercurial", "main.c", false, false);
919+
checkNoHistoryFetchRepo("mercurial", "main.c", false);
885920
// Second try repo which can fetch history of individual files only.
886-
checkNoHistoryFetchRepo("teamware", "header.h", true, true);
921+
checkNoHistoryFetchRepo("teamware", "header.h", true);
887922
}
888923

889924
/**

opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexerTest.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,35 +320,39 @@ public void reset() {
320320
/**
321321
* Test that reindex after changing a file does not wipe out history index
322322
* for this file. This is important for the incremental history indexing.
323-
* @throws Exception
324323
*/
325324
@Test
326325
@EnabledForRepository(MERCURIAL)
327326
void testRemoveFileOnFileChange() throws Exception {
328327
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
329328

329+
String path = "/mercurial/bar.txt";
330+
330331
TestRepository testrepo = new TestRepository();
331332
testrepo.create(HistoryGuru.class.getResourceAsStream("repositories.zip"));
332333

333334
env.setSourceRoot(testrepo.getSourceRoot());
334335
env.setDataRoot(testrepo.getDataRoot());
335336
env.setRepositories(testrepo.getSourceRoot());
336337

338+
// Create history cache.
339+
Indexer.getInstance().prepareIndexer(env, true, true,
340+
false, null, List.of("mercurial"));
341+
File historyFile = new File(env.getDataRootPath(),
342+
TandemPath.join("historycache" + path, ".gz"));
343+
assertTrue(historyFile.exists(), String.format("history cache for %s has to exist", path));
344+
337345
// create index
338346
Project project = new Project("mercurial", "/mercurial");
339347
IndexDatabase idb = new IndexDatabase(project);
340348
assertNotNull(idb);
341-
String path = "/mercurial/bar.txt";
342349
RemoveIndexChangeListener listener = new RemoveIndexChangeListener(path);
343350
idb.addIndexChangedListener(listener);
344351
idb.update();
345352
assertEquals(5, listener.filesToAdd.size());
346353
listener.reset();
347354

348355
// Change a file so that it gets picked up by the indexer.
349-
File historyFile = new File(env.getDataRootPath(),
350-
TandemPath.join("historycache" + path, ".gz"));
351-
assertTrue(historyFile.exists(), String.format("history cache for %s has to exist", path));
352356
File bar = new File(testrepo.getSourceRoot() + File.separator + "mercurial", "bar.txt");
353357
assertTrue(bar.exists());
354358
try (BufferedWriter bw = new BufferedWriter(new FileWriter(bar, true))) {

0 commit comments

Comments
 (0)