Skip to content

Commit b82c5e9

Browse files
author
Vladimir Kotal
committed
enforce timeout for file processing in ctags
fixes #2812
1 parent a6cd125 commit b82c5e9

File tree

4 files changed

+79
-13
lines changed

4 files changed

+79
-13
lines changed

opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/Ctags.java

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@
3434
import java.nio.charset.StandardCharsets;
3535
import java.util.ArrayList;
3636
import java.util.List;
37+
import java.util.Timer;
38+
import java.util.TimerTask;
3739
import java.util.logging.Level;
3840
import java.util.logging.Logger;
39-
import org.opengrok.indexer.configuration.RuntimeEnvironment;
4041
import org.opengrok.indexer.logger.LoggerFactory;
4142
import org.opengrok.indexer.util.IOUtils;
4243
import org.opengrok.indexer.util.SourceSplitter;
@@ -52,13 +53,13 @@ public class Ctags implements Resettable {
5253

5354
private volatile boolean closing;
5455
private Process ctags;
55-
private Thread errThread;
5656
private OutputStreamWriter ctagsIn;
5757
private BufferedReader ctagsOut;
5858
private static final String CTAGS_FILTER_TERMINATOR = "__ctags_done_with_file__";
5959
private String binary;
6060
private String CTagsExtraOptionsFile = null;
6161
private int tabSize;
62+
private int timeout = 0; // in seconds
6263

6364
private boolean junit_testing = false;
6465

@@ -92,6 +93,14 @@ public void setCTagsExtraOptionsFile(String CTagsExtraOptionsFile) {
9293
this.CTagsExtraOptionsFile = CTagsExtraOptionsFile;
9394
}
9495

96+
public void setTimeout(int timeout) {
97+
this.timeout = timeout;
98+
}
99+
100+
public int getTimeout() {
101+
return this.timeout;
102+
}
103+
95104
/**
96105
* Resets the instance for use for another file but without closing any
97106
* running ctags instance.
@@ -116,7 +125,6 @@ public void close() throws IOException {
116125
}
117126

118127
private void initialize() throws IOException {
119-
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
120128
ProcessBuilder processBuilder;
121129
List<String> command = new ArrayList<>();
122130

@@ -197,14 +205,14 @@ private void initialize() throws IOException {
197205
ctagsOut = new BufferedReader(new InputStreamReader(ctags.getInputStream(),
198206
StandardCharsets.UTF_8));
199207

200-
errThread = new Thread(() -> {
208+
Thread errThread = new Thread(() -> {
201209
try (BufferedReader error = new BufferedReader(new InputStreamReader(ctags.getErrorStream(),
202210
StandardCharsets.UTF_8))) {
203211
String s;
204212
while ((s = error.readLine()) != null) {
205213
if (s.length() > 0) {
206214
LOGGER.log(Level.WARNING, "Error from ctags: {0}",
207-
s);
215+
s);
208216
}
209217
if (closing) {
210218
break;
@@ -369,7 +377,7 @@ public Definitions doCtags(String file) throws IOException,
369377
LOGGER.log(Level.WARNING, "Ctags process exited with exit value {0}",
370378
exitValue);
371379
// Throw the following to indicate non-I/O error for retry.
372-
throw new InterruptedException("ctags died");
380+
throw new InterruptedException("ctags process died");
373381
} catch (IllegalThreadStateException exp) {
374382
// The ctags process is still running.
375383
}
@@ -390,7 +398,35 @@ public Definitions doCtags(String file) throws IOException,
390398
if (Thread.interrupted()) {
391399
throw new InterruptedException("flush()");
392400
}
401+
402+
int timeout = this.timeout * 1000;
403+
Timer timer = null;
404+
/*
405+
* Setup timer thread to make sure the ctags process completes and the indexer can
406+
* make progress instead of hanging the whole operation.
407+
*/
408+
if (timeout != 0) {
409+
timer = new Timer();
410+
timer.schedule(new TimerTask() {
411+
@Override public void run() {
412+
LOGGER.log(Level.WARNING,
413+
String.format("Terminating ctags process for file '%s' " +
414+
"due to timeout %d seconds", file, timeout / 1000));
415+
try {
416+
close();
417+
} catch (IOException e) {
418+
LOGGER.log(Level.WARNING, "Failed to terminate overly long ctags command");
419+
}
420+
}
421+
}, timeout);
422+
}
423+
393424
readTags(rdr);
425+
426+
if (timer != null) {
427+
timer.cancel();
428+
}
429+
394430
ret = rdr.getDefinitions();
395431
} catch (IOException ex) {
396432
/*

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ public final class Configuration {
207207
private int tabSize;
208208
private int commandTimeout; // in seconds
209209
private int interactiveCommandTimeout; // in seconds
210+
private int ctagsTimeout; // in seconds
210211
private boolean scopesEnabled;
211212
private boolean projectsEnabled;
212213
private boolean foldingEnabled;
@@ -376,6 +377,24 @@ public void setInteractiveCommandTimeout(int commandTimeout) throws IllegalArgum
376377
this.interactiveCommandTimeout = commandTimeout;
377378
}
378379

380+
public int getCtagsTimeout() {
381+
return ctagsTimeout;
382+
}
383+
384+
/**
385+
* Set the ctags timeout to a new value.
386+
*
387+
* @param timeout the new value
388+
* @throws IllegalArgumentException when the timeout is negative
389+
*/
390+
public void setCtagsTimeout(int timeout) throws IllegalArgumentException {
391+
if (commandTimeout < 0) {
392+
throw new IllegalArgumentException(
393+
String.format(NEGATIVE_NUMBER_ERROR, "ctagsTimeout", timeout));
394+
}
395+
this.ctagsTimeout = timeout;
396+
}
397+
379398
public String getStatisticsFilePath() {
380399
return statisticsFilePath;
381400
}
@@ -411,13 +430,10 @@ public void setGroupsCollapseThreshold(int groupsCollapseThreshold) throws Illeg
411430
}
412431

413432
/**
414-
* Creates a new instance of Configuration.
433+
* Creates a new instance of Configuration with default values.
415434
*/
416435
public Configuration() {
417-
/**
418-
* This list of calls is sorted alphabetically so please keep it.
419-
*/
420-
// defaults for an opengrok instance configuration
436+
// This list of calls is sorted alphabetically so please keep it.
421437
cmds = new HashMap<>();
422438
setAllowedSymlinks(new HashSet<>());
423439
setAuthorizationWatchdogEnabled(false);
@@ -430,6 +446,7 @@ public Configuration() {
430446
setContextLimit((short) 10);
431447
//contextSurround is default(short)
432448
//ctags is default(String)
449+
setCtagsTimeout(30);
433450
setCurrentIndexedCollapseThreshold(27);
434451
setDataRoot(null);
435452
setDisplayRepositories(true);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,14 @@ public int getInteractiveCommandTimeout() {
301301
public void setInteractiveCommandTimeout(int timeout) {
302302
setConfigurationValue("interactiveCommandTimeout", timeout);
303303
}
304+
305+
public int getCtagsTimeout() {
306+
return (int) getConfigurationValue("ctagsTimeout");
307+
}
308+
309+
public void setCtagsTimeout(int timeout) {
310+
setConfigurationValue("ctagsTimeout", timeout);
311+
}
304312

305313
public Statistics getStatistics() {
306314
return statistics;

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,19 +733,24 @@ private void removeFile(boolean removeHistory) throws IOException {
733733
*/
734734
private void addFile(File file, String path, Ctags ctags)
735735
throws IOException, InterruptedException {
736+
737+
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
736738
AbstractAnalyzer fa = getAnalyzerFor(file, path);
737739

738740
for (IndexChangedListener listener : listeners) {
739741
listener.fileAdd(path, fa.getClass().getSimpleName());
740742
}
741743

742744
ctags.setTabSize(project != null ? project.getTabSize() : 0);
745+
if (env.getCtagsTimeout() != 0) {
746+
ctags.setTimeout(env.getCtagsTimeout());
747+
}
743748
if (ctags.getBinary() != null) {
744749
fa.setCtags(ctags);
745750
}
746751
fa.setProject(Project.getProject(path));
747-
fa.setScopesEnabled(RuntimeEnvironment.getInstance().isScopesEnabled());
748-
fa.setFoldingEnabled(RuntimeEnvironment.getInstance().isFoldingEnabled());
752+
fa.setScopesEnabled(env.isScopesEnabled());
753+
fa.setFoldingEnabled(env.isFoldingEnabled());
749754

750755
Document doc = new Document();
751756
try (Writer xrefOut = newXrefWriter(fa, path)) {

0 commit comments

Comments
 (0)