Skip to content

Commit d594176

Browse files
committed
avoid re-opening index directory
fixes #4009
1 parent 5b91bba commit d594176

File tree

10 files changed

+159
-205
lines changed

10 files changed

+159
-205
lines changed

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

+43-19
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ public final class RuntimeEnvironment {
108108
private final LazilyInstantiate<IndexSearcherFactory> lzIndexSearcherFactory;
109109

110110
private final Map<Project, List<RepositoryInfo>> repository_map = new ConcurrentHashMap<>();
111+
/**
112+
* Map of project name (or empty string in case of project-less configuration) to SearcherManager object.
113+
*/
111114
private final Map<String, SearcherManager> searcherManagerMap = new ConcurrentHashMap<>();
112115

113116
private String configURI;
@@ -151,8 +154,7 @@ public List<String> getSubFiles() {
151154
private final Map<String, FileCollector> fileCollectorMap = new HashMap<>();
152155

153156
/**
154-
* Creates a new instance of RuntimeEnvironment. Private to ensure a
155-
* singleton anti-pattern.
157+
* Creates a new instance of RuntimeEnvironment. Private to ensure a singleton anti-pattern.
156158
*/
157159
private RuntimeEnvironment() {
158160
configuration = new Configuration();
@@ -438,20 +440,18 @@ public void setSourceRoot(String sourceRoot) {
438440
}
439441

440442
/**
441-
* Returns a path relative to source root. This would just be a simple
442-
* substring operation, except we need to support symlinks outside the
443-
* source root.
443+
* Returns a path relative to source root. This would just be a simple substring operation,
444+
* except we need to support symlinks outside the source root.
444445
*
445446
* @param file A file to resolve
446447
* @return Path relative to source root
447448
* @throws IOException If an IO error occurs
448449
* @throws FileNotFoundException if the file is not relative to source root
449450
* or if {@code sourceRoot} is not defined
450-
* @throws ForbiddenSymlinkException if symbolic-link checking encounters
451-
* an ineligible link
451+
* @throws ForbiddenSymlinkException if symbolic-link checking encounters an ineligible link
452452
*/
453-
public String getPathRelativeToSourceRoot(File file)
454-
throws IOException, ForbiddenSymlinkException {
453+
public String getPathRelativeToSourceRoot(File file) throws IOException, ForbiddenSymlinkException {
454+
455455
String sourceRoot = getSourceRootPath();
456456
if (sourceRoot == null) {
457457
throw new FileNotFoundException("sourceRoot is not defined");
@@ -1809,27 +1809,51 @@ public void maybeRefreshIndexSearchers() {
18091809
}
18101810

18111811
/**
1812-
* Get IndexSearcher for given project.
1812+
* Get IndexSearcher for given project or global IndexSearcher.
1813+
* Wrapper of {@link #getSuperIndexSearcher(String)}. Make sure to release the returned
1814+
* {@link SuperIndexSearcher} instance.
1815+
* @param file file object
1816+
* @return SuperIndexSearcher instance
1817+
* @throws IOException on error when reading
1818+
*/
1819+
public SuperIndexSearcher getSuperIndexSearcher(File file) throws IOException {
1820+
String name = "";
1821+
if (RuntimeEnvironment.getInstance().hasProjects()) {
1822+
Project p = Project.getProject(file);
1823+
if (p != null) {
1824+
name = p.getName();
1825+
} else {
1826+
throw new IOException(String.format("project for '%s' not found", file));
1827+
}
1828+
}
1829+
1830+
return getSuperIndexSearcher(name);
1831+
}
1832+
1833+
/**
1834+
* Get IndexSearcher for given project or global IndexSearcher.
18131835
* Each IndexSearcher is born from a SearcherManager object. There is one SearcherManager for every project.
18141836
* This schema makes it possible to reuse IndexSearcher/IndexReader objects so the heavy lifting
1815-
* (esp. system calls) performed in {@code FSDirectory} and {@code DirectoryReader} happens only once for a project.
1837+
* (esp. system calls) performed in {@code FSDirectory} and {@code DirectoryReader} happens only once
1838+
* for given index.
1839+
* <p>
18161840
* The caller has to make sure that the IndexSearcher is returned to the SearcherManager.
18171841
* This is done with {@code searcherManagerInstance.release(indexSearcherInstance);}
18181842
* The return of the IndexSearcher should happen only after the search result data are read fully.
18191843
*
1820-
* @param projectName project
1821-
* @return SearcherManager for given project
1844+
* @param searcherName project name or empty string for project-less configuration
1845+
* @return SuperIndexSearcher instance
18221846
* @throws IOException I/O exception
18231847
*/
18241848
@SuppressWarnings("java:S2095")
1825-
public SuperIndexSearcher getIndexSearcher(String projectName) throws IOException {
1849+
public SuperIndexSearcher getSuperIndexSearcher(String searcherName) throws IOException {
18261850

1827-
SearcherManager mgr = searcherManagerMap.get(projectName);
1851+
SearcherManager mgr = searcherManagerMap.get(searcherName);
18281852
if (mgr == null) {
18291853
File indexDir = new File(getDataRootPath(), IndexDatabase.INDEX_DIR);
1830-
Directory dir = FSDirectory.open(new File(indexDir, projectName).toPath());
1854+
Directory dir = FSDirectory.open(new File(indexDir, searcherName).toPath());
18311855
mgr = new SearcherManager(dir, getSuperIndexSearcherFactory());
1832-
searcherManagerMap.put(projectName, mgr);
1856+
searcherManagerMap.put(searcherName, mgr);
18331857
}
18341858

18351859
SuperIndexSearcher searcher = (SuperIndexSearcher) mgr.acquire();
@@ -1868,7 +1892,7 @@ public void refreshSearcherManagerMap() {
18681892

18691893
/**
18701894
* Return collection of IndexReader objects as MultiReader object for given list of projects.
1871-
* The caller is responsible for releasing the IndexSearcher objects.
1895+
* The caller is responsible for releasing the {@link SuperIndexSearcher} objects.
18721896
*
18731897
* @param projects list of projects
18741898
* @param searcherList each SuperIndexSearcher produced will be put into this list
@@ -1882,7 +1906,7 @@ public MultiReader getMultiReader(SortedSet<String> projects, List<SuperIndexSea
18821906
// TODO might need to rewrite to Project instead of String, need changes in projects.jspf too.
18831907
for (String proj : projects) {
18841908
try {
1885-
SuperIndexSearcher searcher = getIndexSearcher(proj);
1909+
SuperIndexSearcher searcher = getSuperIndexSearcher(proj);
18861910
subreaders[ii++] = searcher.getIndexReader();
18871911
searcherList.add(searcher);
18881912
} catch (IOException | NullPointerException ex) {

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

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
package org.opengrok.indexer.configuration;
2424

25+
import java.io.IOException;
2526
import java.util.concurrent.ExecutorService;
2627
import org.apache.lucene.index.IndexReader;
2728
import org.apache.lucene.search.IndexSearcher;
@@ -51,4 +52,8 @@ public void setSearcherManager(SearcherManager s) {
5152
public SearcherManager getSearcherManager() {
5253
return (searcherManager);
5354
}
55+
56+
public void release() throws IOException {
57+
getSearcherManager().release(this);
58+
}
5459
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
/**
2929
* Factory for producing IndexSearcher objects.
30-
* This is used inside getIndexSearcher() to produce new SearcherManager objects
30+
* This is used inside getSuperIndexSearcher() to produce new SearcherManager objects
3131
* to make sure the searcher threads are constrained to single thread pool.
3232
* @author vkotal
3333
*/

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

+5-12
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,16 @@
3838

3939
import org.apache.lucene.document.DateTools;
4040
import org.apache.lucene.document.Document;
41-
import org.apache.lucene.index.IndexReader;
4241
import org.apache.lucene.queryparser.classic.ParseException;
4342
import org.apache.lucene.queryparser.classic.QueryParser;
44-
import org.apache.lucene.search.IndexSearcher;
4543
import org.apache.lucene.search.Query;
4644
import org.apache.lucene.search.ScoreDoc;
4745
import org.apache.lucene.search.Sort;
4846
import org.apache.lucene.search.SortField;
4947
import org.apache.lucene.search.TopFieldDocs;
5048
import org.opengrok.indexer.analysis.CompatibleAnalyser;
5149
import org.opengrok.indexer.configuration.RuntimeEnvironment;
52-
import org.opengrok.indexer.index.IndexDatabase;
50+
import org.opengrok.indexer.configuration.SuperIndexSearcher;
5351
import org.opengrok.indexer.logger.LoggerFactory;
5452
import org.opengrok.indexer.search.QueryBuilder;
5553

@@ -93,17 +91,12 @@ public class DirectoryHistoryReader {
9391
* @throws IOException when index cannot be accessed
9492
*/
9593
public DirectoryHistoryReader(String path) throws IOException {
96-
IndexReader indexReader = null;
94+
SuperIndexSearcher searcher = null;
9795
try {
9896
// Prepare for index search.
9997
String srcRoot = RuntimeEnvironment.getInstance().getSourceRootPath();
100-
indexReader = IndexDatabase.getIndexReader(path);
101-
if (indexReader == null) {
102-
throw new IOException(String.format("Could not locate index database for '%s'", path));
103-
}
10498
// The search results will be sorted by date.
105-
IndexSearcher searcher = RuntimeEnvironment.getInstance().
106-
getIndexSearcherFactory().newSearcher(indexReader);
99+
searcher = RuntimeEnvironment.getInstance().getSuperIndexSearcher(new File(srcRoot, path));
107100
SortField sfield = new SortField(QueryBuilder.DATE, SortField.Type.STRING, true);
108101
Sort sort = new Sort(sfield);
109102
QueryParser qparser = new QueryParser(QueryBuilder.PATH, new CompatibleAnalyser());
@@ -166,9 +159,9 @@ public DirectoryHistoryReader(String path) throws IOException {
166159
// into history object.
167160
history = new History(entries);
168161
} finally {
169-
if (indexReader != null) {
162+
if (searcher != null) {
170163
try {
171-
indexReader.close();
164+
searcher.release();
172165
} catch (Exception ex) {
173166
LOGGER.log(Level.WARNING,
174167
String.format("An error occurred while closing index reader for '%s'", path), ex);

opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java

+25-65
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
import org.apache.lucene.index.TermsEnum;
7777
import org.apache.lucene.queryparser.classic.ParseException;
7878
import org.apache.lucene.search.DocIdSetIterator;
79-
import org.apache.lucene.search.IndexSearcher;
8079
import org.apache.lucene.search.Query;
8180
import org.apache.lucene.search.TopDocs;
8281
import org.apache.lucene.store.AlreadyClosedException;
@@ -99,6 +98,7 @@
9998
import org.opengrok.indexer.configuration.PathAccepter;
10099
import org.opengrok.indexer.configuration.Project;
101100
import org.opengrok.indexer.configuration.RuntimeEnvironment;
101+
import org.opengrok.indexer.configuration.SuperIndexSearcher;
102102
import org.opengrok.indexer.history.FileCollector;
103103
import org.opengrok.indexer.history.HistoryGuru;
104104
import org.opengrok.indexer.history.Repository;
@@ -1848,45 +1848,11 @@ public int getNumFiles() throws IOException {
18481848
}
18491849
}
18501850

1851-
/**
1852-
* Get an indexReader for the Index database where a given file.
1853-
*
1854-
* @param path the file to get the database for
1855-
* @return The index database where the file should be located or {@code null} if it cannot be located.
1856-
*/
1857-
@SuppressWarnings("java:S2095")
1858-
@Nullable
1859-
public static IndexReader getIndexReader(String path) {
1860-
IndexReader ret = null;
1861-
1862-
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1863-
File indexDir = new File(env.getDataRootFile(), INDEX_DIR);
1864-
1865-
if (env.hasProjects()) {
1866-
Project p = Project.getProject(path);
1867-
if (p == null) {
1868-
return null;
1869-
}
1870-
indexDir = new File(indexDir, p.getPath());
1871-
}
1872-
try {
1873-
FSDirectory fdir = FSDirectory.open(indexDir.toPath(), NoLockFactory.INSTANCE);
1874-
if (indexDir.exists() && DirectoryReader.indexExists(fdir)) {
1875-
ret = DirectoryReader.open(fdir);
1876-
}
1877-
} catch (Exception ex) {
1878-
LOGGER.log(Level.SEVERE, "Failed to open index: {0}", indexDir.getAbsolutePath());
1879-
LOGGER.log(Level.FINE, "Stack Trace: ", ex);
1880-
}
1881-
return ret;
1882-
}
1883-
18841851
/**
18851852
* Get the latest definitions for a file from the index.
18861853
*
18871854
* @param file the file whose definitions to find
1888-
* @return definitions for the file, or {@code null} if they could not be
1889-
* found
1855+
* @return definitions for the file, or {@code null} if they could not be found
18901856
* @throws IOException if an error happens when accessing the index
18911857
* @throws ParseException if an error happens when building the Lucene query
18921858
* @throws ClassNotFoundException if the class for the stored definitions
@@ -1909,11 +1875,13 @@ public static Definitions getDefinitions(File file) throws ParseException, IOExc
19091875

19101876
/**
19111877
* @param file File object for a file under source root
1912-
* @return Document object for the file or {@code null}
1878+
* @return Document object for the file or {@code null} if no document was found
19131879
* @throws IOException on I/O error
19141880
* @throws ParseException on problem with building Query
19151881
*/
1916-
public static Document getDocument(File file) throws IOException, ParseException {
1882+
@Nullable
1883+
public static Document getDocument(File file) throws ParseException, IOException {
1884+
19171885
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
19181886
String path;
19191887
try {
@@ -1925,36 +1893,28 @@ public static Document getDocument(File file) throws IOException, ParseException
19251893
// Sanitize Windows path delimiters in order not to conflict with Lucene escape character.
19261894
path = path.replace("\\", "/");
19271895

1928-
try (IndexReader indexReader = getIndexReader(path)) {
1929-
return getDocument(path, indexReader);
1930-
}
1931-
}
1932-
1933-
@Nullable
1934-
private static Document getDocument(String path, IndexReader indexReader) throws ParseException, IOException {
1935-
if (indexReader == null) {
1936-
// No index, no document..
1937-
return null;
1938-
}
1939-
19401896
Document doc;
19411897
Query q = new QueryBuilder().setPath(path).build();
1942-
IndexSearcher searcher = RuntimeEnvironment.getInstance().getIndexSearcherFactory().newSearcher(indexReader);
1943-
Statistics stat = new Statistics();
1944-
TopDocs top = searcher.search(q, 1);
1945-
stat.report(LOGGER, Level.FINEST, "search via getDocument() done",
1946-
"search.latency", new String[]{"category", "getdocument",
1947-
"outcome", top.totalHits.value == 0 ? "empty" : "success"});
1948-
if (top.totalHits.value == 0) {
1949-
// No hits, no document...
1950-
return null;
1951-
}
1952-
doc = searcher.doc(top.scoreDocs[0].doc);
1953-
String foundPath = doc.get(QueryBuilder.PATH);
1898+
SuperIndexSearcher searcher = env.getSuperIndexSearcher(file);
1899+
try {
1900+
Statistics stat = new Statistics();
1901+
TopDocs top = searcher.search(q, 1);
1902+
stat.report(LOGGER, Level.FINEST, "search via getDocument() done",
1903+
"search.latency", new String[]{"category", "getdocument",
1904+
"outcome", top.totalHits.value == 0 ? "empty" : "success"});
1905+
if (top.totalHits.value == 0) {
1906+
// No hits, no document...
1907+
return null;
1908+
}
1909+
doc = searcher.doc(top.scoreDocs[0].doc);
1910+
String foundPath = doc.get(QueryBuilder.PATH);
19541911

1955-
// Only use the document if we found an exact match.
1956-
if (!path.equals(foundPath)) {
1957-
return null;
1912+
// Only use the document if we found an exact match.
1913+
if (!path.equals(foundPath)) {
1914+
return null;
1915+
}
1916+
} finally {
1917+
searcher.release();
19581918
}
19591919

19601920
return doc;

0 commit comments

Comments
 (0)