Skip to content

Commit 9b94a33

Browse files
committed
Recognize InterruptedException, and ensure Ctags does block indefinitely
1 parent dc939fe commit 9b94a33

18 files changed

+308
-162
lines changed

src/org/opensolaris/opengrok/analysis/AnalyzerGuru.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,11 @@ public static FileAnalyzer getAnalyzer(InputStream in, String file) throws IOExc
383383
* @param fa The analyzer to use on the file
384384
* @param xrefOut Where to write the xref (possibly {@code null})
385385
* @throws IOException If an exception occurs while collecting the data
386+
* @throws InterruptedException if a timeout occurs
386387
*/
387388
public void populateDocument(Document doc, File file, String path,
388-
FileAnalyzer fa, Writer xrefOut)
389-
throws IOException {
389+
FileAnalyzer fa, Writer xrefOut) throws IOException,
390+
InterruptedException {
390391

391392
String date = DateTools.timeToString(file.lastModified(),
392393
DateTools.Resolution.MILLISECOND);

src/org/opensolaris/opengrok/analysis/Ctags.java

Lines changed: 115 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
import java.io.StringReader;
3333
import java.util.ArrayList;
3434
import java.util.List;
35+
import java.util.concurrent.ScheduledFuture;
36+
import java.util.concurrent.ScheduledThreadPoolExecutor;
37+
import java.util.concurrent.TimeUnit;
3538
import java.util.logging.Level;
3639
import java.util.logging.Logger;
3740
import org.opensolaris.opengrok.configuration.RuntimeEnvironment;
@@ -48,6 +51,16 @@ public class Ctags {
4851
private static final Logger LOGGER = LoggerFactory.getLogger(Ctags.class);
4952
private static final long SIGNALWAIT_MS = 5000;
5053

54+
/**
55+
* Wait up to 60 seconds for ctags to run, which is a very long time for a
56+
* single run but is really intended for detecting blocked ctags processes
57+
* as happened with @tuxillo's testing of
58+
* <a href="https://github.com/oracle/opengrok/pull/1936">
59+
* Feature/deferred_ops</a>.
60+
*/
61+
private static final long CTAGSWAIT_S = 60;
62+
63+
private final ScheduledThreadPoolExecutor schedExecutor;
5164
private final CtagsBuffer buffer = new CtagsBuffer();
5265
private final Object syncRoot = new Object();
5366
private volatile boolean closing;
@@ -65,6 +78,19 @@ public class Ctags {
6578

6679
private boolean junit_testing = false;
6780

81+
/**
82+
* Initializes an instance using the specified, required executor which
83+
* will be used to track ctags timeouts to avoid blockages.
84+
* <p>(The {@code schedExecutor} is not owned by {@link Ctags}.)
85+
* @param schedExecutor required, defined instance
86+
*/
87+
public Ctags(ScheduledThreadPoolExecutor schedExecutor) {
88+
if (schedExecutor == null) {
89+
throw new IllegalArgumentException("`schedExecutor' is null");
90+
}
91+
this.schedExecutor = schedExecutor;
92+
}
93+
6894
/**
6995
* Gets a value indicating if a subprocess of ctags was started and either:
7096
* 1) it is not alive; or 2) any necessary supporting thread is not alive.
@@ -311,56 +337,33 @@ private void initialize() throws IOException {
311337

312338
@Override
313339
public void run() {
314-
StringBuilder sb = new StringBuilder();
315340
try (BufferedReader error = new BufferedReader(
316341
new InputStreamReader(ctags.getErrorStream()))) {
317342
String s;
318343
while ((s = error.readLine()) != null) {
319-
sb.append(s);
320-
sb.append('\n');
344+
if (s.length() > 0) {
345+
LOGGER.log(Level.WARNING, "Error from ctags: {0}",
346+
s);
347+
}
321348
if (closing) break;
322349
}
323350
} catch (IOException exp) {
324351
LOGGER.log(Level.WARNING, "Got an exception reading ctags error stream: ", exp);
325352
}
326-
if (sb.length() > 0) {
327-
LOGGER.log(Level.WARNING, "Error from ctags: {0}", sb.toString());
328-
}
329353
}
330354
});
331355
errThread.setDaemon(true);
332356
errThread.start();
333357

334-
outThread = new Thread(() -> {
335-
while (!closing) {
336-
synchronized(syncRoot) {
337-
while (!signalled && !closing) {
338-
try {
339-
syncRoot.wait(SIGNALWAIT_MS);
340-
} catch (InterruptedException e) {
341-
LOGGER.log(Level.WARNING,
342-
"Ctags direct client unexpectedly interrupted");
343-
}
344-
}
345-
signalled = false;
346-
}
347-
if (closing) return;
348-
349-
CtagsReader rdr = new CtagsReader();
350-
readTags(rdr);
351-
Definitions defs = rdr.getDefinitions();
352-
try {
353-
buffer.put(defs);
354-
} catch (InterruptedException ex) {
355-
// ignore
356-
}
357-
}
358-
});
359-
outThread.setDaemon(true);
360-
outThread.start();
358+
if (outThread == null || !outThread.isAlive()) {
359+
outThread = new Thread(() -> outThreadStart());
360+
outThread.setDaemon(true);
361+
outThread.start();
362+
}
361363
}
362364

363-
public Definitions doCtags(String file) throws IOException {
365+
public Definitions doCtags(String file) throws IOException,
366+
InterruptedException {
364367
boolean ctagsRunning = false;
365368
if (ctags != null) {
366369
try {
@@ -380,23 +383,36 @@ public Definitions doCtags(String file) throws IOException {
380383
initialize();
381384
}
382385

383-
Definitions ret = null;
384-
if (file.length() > 0 && !"\n".equals(file)) {
385-
synchronized(syncRoot) {
386-
signalled = true;
387-
syncRoot.notify();
388-
}
386+
if (file.length() < 1 || "\n".equals(file)) return null;
387+
388+
synchronized(syncRoot) {
389+
signalled = true;
390+
syncRoot.notify();
391+
}
389392

393+
Thread clientThread = Thread.currentThread();
394+
ScheduledFuture futureTimeout = schedExecutor.schedule(() -> {
395+
clientThread.interrupt();
396+
outThread.interrupt();
397+
LOGGER.log(Level.WARNING, "Ctags futureTimeout executed.");
398+
}, CTAGSWAIT_S, TimeUnit.SECONDS);
399+
400+
Definitions ret;
401+
try {
390402
ctagsIn.write(file);
403+
if (Thread.interrupted()) throw new InterruptedException("write()");
391404
ctagsIn.flush();
392-
try {
393-
ret = buffer.take();
394-
} catch (InterruptedException e) {
395-
LOGGER.log(Level.WARNING,
396-
"Ctags indirect client unexpectedly interrupted");
397-
}
405+
if (Thread.interrupted()) throw new InterruptedException("flush()");
406+
ret = buffer.take();
407+
} finally {
408+
futureTimeout.cancel(false);
398409
}
399410

411+
/*
412+
* If the timeout could not be canceled in time, then act as if no
413+
* results were obtained.
414+
*/
415+
if (Thread.interrupted()) throw new InterruptedException("late");
400416
return ret;
401417
}
402418

@@ -442,15 +458,23 @@ public void destroy() {
442458
};
443459

444460
CtagsReader rdr = new CtagsReader();
445-
readTags(rdr);
461+
try {
462+
readTags(rdr);
463+
} catch (InterruptedException ex) {
464+
LOGGER.log(Level.SEVERE, "readTags() test", ex);
465+
}
446466
Definitions ret = rdr.getDefinitions();
447467
return ret;
448468
}
449469

450-
private void readTags(CtagsReader reader) {
470+
private void readTags(CtagsReader reader) throws InterruptedException {
451471
try {
452472
do {
453473
String tagLine = ctagsOut.readLine();
474+
if (Thread.interrupted()) {
475+
throw new InterruptedException("readLine()");
476+
}
477+
454478
//log.fine("Tagline:-->" + tagLine+"<----ONELINE");
455479
if (tagLine == null) {
456480
if (!junit_testing) {
@@ -480,9 +504,52 @@ private void readTags(CtagsReader reader) {
480504

481505
reader.readLine(tagLine);
482506
} while (true);
507+
} catch (InterruptedException e) {
508+
throw e;
483509
} catch (Exception e) {
484510
LOGGER.log(Level.WARNING, "CTags parsing problem: ", e);
485511
}
486512
LOGGER.severe("CTag reader cycle was interrupted!");
487513
}
514+
515+
private void outThreadStart() {
516+
while (!closing) {
517+
synchronized(syncRoot) {
518+
while (!signalled && !closing) {
519+
try {
520+
syncRoot.wait(SIGNALWAIT_MS);
521+
} catch (InterruptedException e) {
522+
LOGGER.log(Level.WARNING,
523+
"Ctags outThread unexpectedly interrupted--{0}",
524+
e.getMessage());
525+
/*
526+
* Terminating this thread will cause this Ctags
527+
* instance, which appears to be blocked, to be
528+
* reported as closed by isClosed().
529+
*/
530+
return;
531+
}
532+
}
533+
signalled = false;
534+
}
535+
if (closing) return;
536+
537+
CtagsReader rdr = new CtagsReader();
538+
try {
539+
readTags(rdr);
540+
Definitions defs = rdr.getDefinitions();
541+
buffer.put(defs);
542+
} catch (InterruptedException ex) {
543+
LOGGER.log(Level.WARNING,
544+
"Ctags outThread unexpectedly interrupted--{0}",
545+
ex.getMessage());
546+
/*
547+
* Terminating this thread will cause this Ctags instance,
548+
* which appears to be blocked, to be reported as closed by
549+
* isClosed().
550+
*/
551+
return;
552+
}
553+
}
554+
}
488555
}

src/org/opensolaris/opengrok/analysis/FileAnalyzer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,10 @@ public String getFileTypeName() {
179179
* @param src the input data source
180180
* @param xrefOut where to write the xref (may be {@code null})
181181
* @throws IOException if any I/O error
182+
* @throws InterruptedException if a timeout occurs
182183
*/
183-
public void analyze(Document doc, StreamSource src, Writer xrefOut) throws IOException {
184+
public void analyze(Document doc, StreamSource src, Writer xrefOut)
185+
throws IOException, InterruptedException {
184186
// not used
185187
}
186188

src/org/opensolaris/opengrok/analysis/archive/BZip2Analyzer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
/*
2121
* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
22+
* Portions Copyright (c) 2017, Chris Fraire <[email protected]>.
2223
*/
2324
package org.opensolaris.opengrok.analysis.archive;
2425

@@ -57,7 +58,8 @@ protected BZip2Analyzer(FileAnalyzerFactory factory) {
5758
private FileAnalyzer fa;
5859

5960
@Override
60-
public void analyze(Document doc, StreamSource src, Writer xrefOut) throws IOException {
61+
public void analyze(Document doc, StreamSource src, Writer xrefOut)
62+
throws IOException, InterruptedException {
6163
StreamSource bzSrc = wrap(src);
6264
String path = doc.get("path");
6365
if (path != null

src/org/opensolaris/opengrok/analysis/archive/GZIPAnalyzer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
/*
2121
* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
22+
* Portions Copyright (c) 2017, Chris Fraire <[email protected]>.
2223
*/
2324
package org.opensolaris.opengrok.analysis.archive;
2425

@@ -63,7 +64,8 @@ protected GZIPAnalyzer(FileAnalyzerFactory factory) {
6364
private FileAnalyzer fa;
6465

6566
@Override
66-
public void analyze(Document doc, StreamSource src, Writer xrefOut) throws IOException {
67+
public void analyze(Document doc, StreamSource src, Writer xrefOut)
68+
throws IOException, InterruptedException {
6769
StreamSource gzSrc = wrap(src);
6870
String path = doc.get("path");
6971
if (path != null

src/org/opensolaris/opengrok/analysis/plain/AbstractSourceCodeAnalyzer.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
/*
2121
* Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
22+
* Portions Copyright (c) 2017, Chris Fraire <[email protected]>.
2223
*/
2324
package org.opensolaris.opengrok.analysis.plain;
2425

@@ -66,9 +67,4 @@ protected AbstractSourceCodeAnalyzer(FileAnalyzerFactory factory) {
6667
*/
6768
@Override
6869
protected abstract JFlexXref newXref(Reader reader);
69-
70-
@Override
71-
public void analyze(Document doc, StreamSource src, Writer xrefOut) throws IOException {
72-
super.analyze(doc, src, xrefOut);
73-
}
7470
}

src/org/opensolaris/opengrok/analysis/plain/PlainAnalyzer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ protected Reader getReader(InputStream stream) throws IOException {
7373
}
7474

7575
@Override
76-
public void analyze(Document doc, StreamSource src, Writer xrefOut) throws IOException {
76+
public void analyze(Document doc, StreamSource src, Writer xrefOut)
77+
throws IOException, InterruptedException {
7778
Definitions defs = null;
7879

7980
doc.add(new TextField(QueryBuilder.FULL, getReader(src.getStream())));

0 commit comments

Comments
 (0)