Skip to content

Commit e829566

Browse files
committed
Support ctags for historical revisions
- Add webappCtags configuration flag, with --webappCtags switch, to indicate if the webapp is eligible to run ctags. - Add Repository.getHistoryGet() override to allow sub-classes to override to avoid a full in-memory version; and override for Git and Mercurial. - Revise BoundedBlockingObjectPool as LIFO-to-FIFO so the webapp does not start extraneous instances; Indexer switches to FIFO performance when the queue pool is emptied. - make IndexerParallelizer a lazy property of RuntimeEnvironment, and make the executors of IndexerParallelizer also lazy properties. Move the history-related executors into IndexerParallelizer so the lifecycle of all indexing/history executors are controlled in the same class.
1 parent 968004c commit e829566

File tree

18 files changed

+669
-244
lines changed

18 files changed

+669
-244
lines changed

dev/checkstyle/suppressions.xml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
<?xml version="1.0"?>
2+
<!--
23
4+
CDDL HEADER START
5+
6+
The contents of this file are subject to the terms of the
7+
Common Development and Distribution License (the "License").
8+
You may not use this file except in compliance with the License.
9+
10+
See LICENSE.txt included in this distribution for the specific
11+
language governing permissions and limitations under the License.
12+
13+
When distributing Covered Code, include this CDDL HEADER in each
14+
file and include the License file at LICENSE.txt.
15+
If applicable, add the following below this CDDL HEADER, with the
16+
fields enclosed by brackets "[]" replaced with your own identifying
17+
information: Portions Copyright [yyyy] [name of copyright owner]
18+
19+
CDDL HEADER END
20+
21+
Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
22+
Portions Copyright (c) 2018, Chris Fraire <[email protected]>.
23+
24+
-->
325
<!DOCTYPE suppressions PUBLIC
426
"-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN"
527
"https://checkstyle.org/dtds/suppressions_1_1.dtd">
@@ -9,7 +31,8 @@
931
|CustomExactPhraseScorer\.java|PhrasePositions\.java|PhraseQueue\.java|Summarizer\.java|
1032
|Summary\.java|OGKUnifiedHighlighter\.java|ResponseHeaderFilter\.java|BoundedBlockingObjectPool\.java|
1133
|ObjectPool\.java|ObjectValidator\.java|ObjectFactory\.java|AbstractObjectPool\.java|
12-
|BlockingObjectPool\.java|OGKTextVecField\.java|OGKTextField\.java" />
34+
|BlockingObjectPool\.java|OGKTextVecField\.java|OGKTextField\.java|
35+
|LazilyInstantiate\.java" />
1336

1437
<suppress checks="ParameterNumber" files="CtagsReader\.java|Definitions\.java|PerlLexHelper\.java|
1538
|JFlexXrefUtils\.java|RubyLexHelper\.java|FileAnalyzerFactory\.java|SearchController\.java|

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public final class Configuration {
107107
* Path to {@code ctags} binary.
108108
*/
109109
private String ctags;
110+
private boolean webappCtags;
110111

111112
/**
112113
* A defined value to specify the mandoc binary or else null so that mandoc
@@ -475,6 +476,7 @@ public Configuration() {
475476
// unconditionally later.
476477
setUserPageSuffix("");
477478
setWebappLAF("default");
479+
// webappCtags is default(boolean)
478480
}
479481

480482
public String getRepoCmd(String clazzName) {
@@ -983,6 +985,20 @@ public void setWebappLAF(String webappLAF) {
983985
this.webappLAF = webappLAF;
984986
}
985987

988+
/**
989+
* Gets a value indicating if the web app should run ctags as necessary.
990+
*/
991+
public boolean isWebappCtags() {
992+
return webappCtags;
993+
}
994+
995+
/**
996+
* Sets a value indicating if the web app should run ctags as necessary.
997+
*/
998+
public void setWebappCtags(boolean value) {
999+
this.webappCtags = value;
1000+
}
1001+
9861002
public RemoteSCM getRemoteScmSupported() {
9871003
return remoteScmSupported;
9881004
}

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

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import java.util.concurrent.ExecutorService;
4444
import java.util.concurrent.Executors;
4545
import java.util.concurrent.ThreadFactory;
46-
import java.util.concurrent.TimeUnit;
4746
import java.util.concurrent.locks.Lock;
4847
import java.util.concurrent.locks.ReentrantReadWriteLock;
4948
import java.util.logging.Level;
@@ -65,9 +64,11 @@
6564
import org.opengrok.indexer.index.Filter;
6665
import org.opengrok.indexer.index.IgnoredNames;
6766
import org.opengrok.indexer.index.IndexDatabase;
67+
import org.opengrok.indexer.index.IndexerParallelizer;
6868
import org.opengrok.indexer.logger.LoggerFactory;
6969
import org.opengrok.indexer.util.CtagsUtil;
7070
import org.opengrok.indexer.util.ForbiddenSymlinkException;
71+
import org.opengrok.indexer.util.LazilyInstantiate;
7172
import org.opengrok.indexer.util.PathUtils;
7273
import org.opengrok.indexer.web.Prefix;
7374
import org.opengrok.indexer.web.Statistics;
@@ -88,11 +89,10 @@ public final class RuntimeEnvironment {
8889
private static final String URL_PREFIX = "/source" + Prefix.SEARCH_R + "?";
8990

9091
private Configuration configuration;
91-
private ReentrantReadWriteLock configLock;
92+
private final ReentrantReadWriteLock configLock;
93+
private final LazilyInstantiate<IndexerParallelizer> lzIndexerParallelizer;
94+
private final LazilyInstantiate<ExecutorService> lzSearchExecutor;
9295
private static final RuntimeEnvironment instance = new RuntimeEnvironment();
93-
private static ExecutorService historyExecutor = null;
94-
private static ExecutorService historyRenamedExecutor = null;
95-
private static ExecutorService searchExecutor = null;
9696

9797
private final Map<Project, List<RepositoryInfo>> repository_map = new ConcurrentHashMap<>();
9898
private final Map<String, SearcherManager> searcherManagerMap = new ConcurrentHashMap<>();
@@ -127,43 +127,21 @@ private RuntimeEnvironment() {
127127
configuration = new Configuration();
128128
configLock = new ReentrantReadWriteLock();
129129
watchDog = new WatchDogService();
130+
lzIndexerParallelizer = LazilyInstantiate.using(() ->
131+
new IndexerParallelizer(this));
132+
lzSearchExecutor = LazilyInstantiate.using(() -> newSearchExecutor());
130133
}
131134

132135
/** Instance of authorization framework.*/
133136
private AuthorizationFramework authFramework;
134137

135-
/* Get thread pool used for top-level repository history generation. */
136-
public static synchronized ExecutorService getHistoryExecutor() {
137-
if (historyExecutor == null) {
138-
historyExecutor = Executors.newFixedThreadPool(getInstance().getHistoryParallelism(),
139-
runnable -> {
140-
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
141-
thread.setName("history-handling-" + thread.getId());
142-
return thread;
143-
});
144-
}
145-
146-
return historyExecutor;
147-
}
148-
149-
/* Get thread pool used for history generation of renamed files. */
150-
public static synchronized ExecutorService getHistoryRenamedExecutor() {
151-
if (historyRenamedExecutor == null) {
152-
historyRenamedExecutor = Executors.newFixedThreadPool(getInstance().getHistoryRenamedParallelism(),
153-
runnable -> {
154-
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
155-
thread.setName("renamed-handling-" + thread.getId());
156-
return thread;
157-
});
158-
}
159-
160-
return historyRenamedExecutor;
138+
/** Gets the thread pool used for multi-project searches. */
139+
public ExecutorService getSearchExecutor() {
140+
return lzSearchExecutor.get();
161141
}
162142

163-
/* Get thread pool used for multi-project searches. */
164-
public synchronized ExecutorService getSearchExecutor() {
165-
if (searchExecutor == null) {
166-
searchExecutor = Executors.newFixedThreadPool(
143+
private ExecutorService newSearchExecutor() {
144+
return Executors.newFixedThreadPool(
167145
this.getMaxSearchThreadCount(),
168146
new ThreadFactory() {
169147
@Override
@@ -173,23 +151,6 @@ public Thread newThread(Runnable runnable) {
173151
return thread;
174152
}
175153
});
176-
}
177-
178-
return searchExecutor;
179-
}
180-
181-
public static synchronized void freeHistoryExecutor() {
182-
historyExecutor = null;
183-
}
184-
185-
public static synchronized void destroyRenamedHistoryExecutor() throws InterruptedException {
186-
if (historyRenamedExecutor != null) {
187-
historyRenamedExecutor.shutdown();
188-
// All the jobs should be completed by now however for testing
189-
// we would like to make sure the threads are gone.
190-
historyRenamedExecutor.awaitTermination(1, TimeUnit.MINUTES);
191-
historyRenamedExecutor = null;
192-
}
193154
}
194155

195156
/**
@@ -203,6 +164,10 @@ public static RuntimeEnvironment getInstance() {
203164

204165
public WatchDogService watchDog;
205166

167+
public IndexerParallelizer getIndexerParallelizer() {
168+
return lzIndexerParallelizer.get();
169+
}
170+
206171
private String getCanonicalPath(String s) {
207172
if (s == null) { return null; }
208173
try {
@@ -1092,6 +1057,14 @@ public void setWebappLAF(String laf) {
10921057
setConfigurationValue("webappLAF", laf);
10931058
}
10941059

1060+
/**
1061+
* Gets a value indicating if the web app should run ctags as necessary.
1062+
* @return the value of {@link Configuration#isWebappCtags()}
1063+
*/
1064+
public boolean isWebappCtags() {
1065+
return (boolean)getConfigurationValue("webappCtags");
1066+
}
1067+
10951068
public Configuration.RemoteSCM getRemoteScmSupported() {
10961069
return (Configuration.RemoteSCM)getConfigurationValue("remoteScmSupported");
10971070
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ private History mergeOldAndNewHistory(File cacheFile, History histNew, Repositor
333333
/**
334334
* Store history object (encoded as XML and compressed with gzip) in a file.
335335
*
336-
* @param history history object to store
336+
* @param histNew history object to store
337337
* @param file file to store the history object into
338338
* @param repo repository for the file
339339
* @param mergeHistory whether to merge the history with existing or
@@ -536,9 +536,7 @@ public void store(History history, Repository repository)
536536
final CountDownLatch latch = new CountDownLatch(renamed_map.size());
537537
AtomicInteger renamedFileHistoryCount = new AtomicInteger();
538538
for (final Map.Entry<String, List<HistoryEntry>> map_entry : renamed_map.entrySet()) {
539-
RuntimeEnvironment.getHistoryRenamedExecutor().submit(new Runnable() {
540-
@Override
541-
public void run() {
539+
env.getIndexerParallelizer().getHistoryRenamedExecutor().submit(() -> {
542540
try {
543541
doFileHistory(map_entry.getKey(), map_entry.getValue(),
544542
env, repositoryF,
@@ -552,7 +550,6 @@ public void run() {
552550
} finally {
553551
latch.countDown();
554552
}
555-
}
556553
});
557554
}
558555

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

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,19 @@
1919

2020
/*
2121
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
22-
* Portions Copyright (c) 2017, Chris Fraire <[email protected]>.
22+
* Portions Copyright (c) 2017-2018, Chris Fraire <[email protected]>.
2323
*/
2424
package org.opengrok.indexer.history;
2525

2626
import java.io.BufferedReader;
2727
import java.io.ByteArrayInputStream;
2828
import java.io.ByteArrayOutputStream;
2929
import java.io.File;
30+
import java.io.FileOutputStream;
3031
import java.io.IOException;
3132
import java.io.InputStream;
3233
import java.io.InputStreamReader;
34+
import java.io.OutputStream;
3335
import java.io.Reader;
3436
import java.nio.file.Paths;
3537
import java.text.ParseException;
@@ -46,6 +48,7 @@
4648
import java.util.regex.Pattern;
4749
import org.opengrok.indexer.configuration.RuntimeEnvironment;
4850
import org.opengrok.indexer.logger.LoggerFactory;
51+
import org.opengrok.indexer.util.BufferSink;
4952
import org.opengrok.indexer.util.Executor;
5053
import org.opengrok.indexer.util.StringUtils;
5154

@@ -171,14 +174,18 @@ Executor getRenamedFilesExecutor(final File file, String sinceRevision) throws I
171174
/**
172175
* Try to get file contents for given revision.
173176
*
177+
* @param sink a required target sink
178+
* @param outIterations a required out array for storing the number of calls
179+
* made to {@code sink}
174180
* @param fullpath full pathname of the file
175181
* @param rev revision
176-
* @return contents of the file in revision rev
182+
* @return {@code true} if any contents were found
177183
*/
178-
private InputStream getHistoryRev(String fullpath, String rev) {
179-
InputStream ret = null;
180-
File directory = new File(getDirectoryName());
184+
private boolean getHistoryRev(BufferSink sink, int[] outIterations,
185+
String fullpath, String rev) {
181186

187+
outIterations[0] = 0;
188+
File directory = new File(getDirectoryName());
182189
try {
183190
/*
184191
* Be careful, git uses only forward slashes in its command and output (not in file path).
@@ -197,13 +204,13 @@ private InputStream getHistoryRev(String fullpath, String rev) {
197204
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
198205
int status = executor.exec();
199206

200-
ByteArrayOutputStream out = new ByteArrayOutputStream();
201207
byte[] buffer = new byte[32 * 1024];
202208
try (InputStream in = executor.getOutputStream()) {
203209
int len;
204210
while ((len = in.read(buffer)) != -1) {
205211
if (len > 0) {
206-
out.write(buffer, 0, len);
212+
outIterations[0]++;
213+
sink.write(buffer, 0, len);
207214
}
208215
}
209216
}
@@ -212,22 +219,48 @@ private InputStream getHistoryRev(String fullpath, String rev) {
212219
* If exit value of the process was not 0 then the file did
213220
* not exist or internal git error occured.
214221
*/
215-
if (status == 0) {
216-
ret = new ByteArrayInputStream(out.toByteArray());
217-
} else {
218-
ret = null;
219-
}
222+
return status == 0;
220223
} catch (Exception exp) {
221224
LOGGER.log(Level.SEVERE,
222225
"Failed to get history for file {0} in revision {1}: ",
223226
new Object[]{fullpath, rev, exp.getClass().toString(), exp});
227+
return false;
224228
}
229+
}
225230

226-
return ret;
231+
/**
232+
* Gets the contents of a specific version of a named file into the
233+
* specified target without a full, in-memory buffer.
234+
*
235+
* @param target a required target file which will be overwritten
236+
* @param parent the name of the directory containing the file
237+
* @param basename the name of the file to get
238+
* @param rev the revision to get
239+
* @return {@code true} if contents were found
240+
* @throws java.io.IOException if an I/O error occurs
241+
*/
242+
@Override
243+
public boolean getHistoryGet(File target, String parent, String basename,
244+
String rev) throws IOException {
245+
try (OutputStream out = new FileOutputStream(target)) {
246+
return getHistoryGet((buf, offset, n) -> out.write(buf, offset, n),
247+
parent, basename, rev);
248+
}
227249
}
228250

229251
@Override
230-
public InputStream getHistoryGet(String parent, String basename, String rev) {
252+
public InputStream getHistoryGet(String parent, String basename,
253+
String rev) {
254+
ByteArrayOutputStream out = new ByteArrayOutputStream();
255+
if (getHistoryGet((buf, offset, n) -> out.write(buf, offset, n),
256+
parent, basename, rev)) {
257+
return new ByteArrayInputStream(out.toByteArray());
258+
}
259+
return null;
260+
}
261+
262+
protected boolean getHistoryGet(BufferSink sink, String parent,
263+
String basename, String rev) {
231264
String fullpath;
232265
try {
233266
fullpath = new File(parent, basename).getCanonicalPath();
@@ -238,12 +271,13 @@ public String get() {
238271
return String.format("Failed to get canonical path: %s/%s", parent, basename);
239272
}
240273
});
241-
return null;
274+
return false;
242275
}
243276

244-
InputStream ret = getHistoryRev(fullpath, rev);
245-
246-
if (ret == null) {
277+
int[] iterations = new int[1];
278+
boolean ret = getHistoryRev((buf, offset, n) -> sink.write(buf, offset,
279+
n), iterations, fullpath, rev);
280+
if (!ret && iterations[0] < 1) {
247281
/*
248282
* If we failed to get the contents it might be that the file was
249283
* renamed so we need to find its original name in that revision
@@ -259,10 +293,10 @@ public String get() {
259293
return String.format("Failed to get original revision: %s/%s (revision %s)", parent, basename, rev);
260294
}
261295
});
262-
return null;
296+
return false;
263297
}
264298
if (origpath != null) {
265-
ret = getHistoryRev(origpath, rev);
299+
ret = getHistoryRev(sink, iterations, origpath, rev);
266300
}
267301
}
268302

0 commit comments

Comments
 (0)