Skip to content

Commit bb0160c

Browse files
committed
Detect if ProcessBuilder.start() hangs.
Also: - Short-circuit in Ctags doCtags() early. - Do not support reinitialization if ctags dies but instead allow ObjectPool to handleInvalidReturn() and create a new instance.
1 parent 4c120df commit bb0160c

File tree

2 files changed

+75
-20
lines changed

2 files changed

+75
-20
lines changed

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

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,21 @@ public class Ctags {
5252
private static final long SIGNALWAIT_MS = 5000;
5353

5454
/**
55-
* Wait up to 60 seconds for ctags to run, which is a very long time for a
55+
* Wait up to 90 seconds for ctags to run, which is a very long time for a
5656
* single run but is really intended for detecting blocked ctags processes
5757
* as happened with @tuxillo's testing of
5858
* <a href="https://github.com/oracle/opengrok/pull/1936">
5959
* Feature/deferred_ops</a>.
6060
*/
61-
private static final long CTAGSWAIT_S = 60;
61+
private static final long CTAGSWAIT_S = 90;
6262

6363
private final ScheduledThreadPoolExecutor schedExecutor;
6464
private final CtagsBuffer buffer = new CtagsBuffer();
6565
private final Object syncRoot = new Object();
6666
private volatile boolean closing;
6767
private volatile boolean signalled;
68-
private Process ctags;
68+
private volatile Process ctags;
69+
private volatile IOException startIOException;
6970
private Thread errThread;
7071
private Thread outThread;
7172
private OutputStreamWriter ctagsIn;
@@ -119,13 +120,13 @@ public void close() throws IOException {
119120
if (ctags != null) {
120121
closing = true;
121122
LOGGER.log(Level.FINE, "Destroying ctags command");
122-
ctags.destroy();
123+
ctags.destroyForcibly();
123124
}
124125
}
125126

126-
private void initialize() throws IOException {
127+
private void initialize() throws IOException, InterruptedException {
127128
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
128-
if (processBuilder == null) {
129+
if (true) {
129130
List<String> command = new ArrayList<>();
130131

131132
command.add(binary);
@@ -329,7 +330,62 @@ private void initialize() throws IOException {
329330
processBuilder = new ProcessBuilder(command);
330331
}
331332

332-
ctags = processBuilder.start();
333+
Thread clientThread = Thread.currentThread();
334+
335+
startIOException = null;
336+
// Start ctags on a separate, daemon thread in case start() blocks.
337+
Thread startThread = new Thread(() -> {
338+
try {
339+
Process newCtags = processBuilder.start();
340+
LOGGER.log(Level.FINE, "Executed ctags command re t{0}",
341+
clientThread.getId());
342+
synchronized(syncRoot) {
343+
ctags = newCtags;
344+
syncRoot.notify();
345+
}
346+
} catch (IOException e) {
347+
startIOException = e;
348+
}
349+
});
350+
startThread.setDaemon(true);
351+
startThread.start();
352+
353+
// Set a timeout so the client thread does not wait indefinitely.
354+
ScheduledFuture startTimeout = schedExecutor.schedule(() -> {
355+
LOGGER.log(Level.FINE, "Ctags startTimeout executing re t{0}.",
356+
clientThread.getId());
357+
clientThread.interrupt();
358+
/**
359+
* No point in interrupting startThread, since nothing it executes
360+
* is interruptible.
361+
*/
362+
LOGGER.log(Level.FINE, "Ctags startTimeout executed.");
363+
}, CTAGSWAIT_S, TimeUnit.SECONDS);
364+
365+
// Wait until startThread sets the ctags field, until timeout, or error.
366+
synchronized(syncRoot) {
367+
while (ctags == null) {
368+
if (startIOException != null) {
369+
startTimeout.cancel(false);
370+
throw startIOException;
371+
}
372+
try {
373+
syncRoot.wait(SIGNALWAIT_MS);
374+
} catch (InterruptedException e) {
375+
LOGGER.log(Level.WARNING, "Ctags did not start--{0}",
376+
e.getMessage());
377+
startTimeout.cancel(false);
378+
throw e;
379+
}
380+
}
381+
}
382+
383+
startTimeout.cancel(false);
384+
/*
385+
* If the timeout could not be truly canceled in time, then ignore it.
386+
*/
387+
Thread.interrupted();
388+
333389
ctagsIn = new OutputStreamWriter(ctags.getOutputStream());
334390
ctagsOut = new BufferedReader(new InputStreamReader(ctags.getInputStream()));
335391

@@ -364,38 +420,37 @@ public void run() {
364420

365421
public Definitions doCtags(String file) throws IOException,
366422
InterruptedException {
367-
boolean ctagsRunning = false;
423+
if (file.length() < 1 || "\n".equals(file)) return null;
424+
368425
if (ctags != null) {
369426
try {
370427
int exitValue = ctags.exitValue();
371428
// If it is possible to retrieve exit value without exception
372-
// this means the ctags process is dead so we must restart it.
373-
ctagsRunning = false;
429+
// this means the ctags process is dead.
374430
LOGGER.log(Level.WARNING, "Ctags process exited with exit value {0}",
375431
exitValue);
432+
// Throw the following to indicate non-I/O error for retry.
433+
throw new InterruptedException("ctags died");
376434
} catch (IllegalThreadStateException exp) {
377-
ctagsRunning = true;
378435
// The ctags process is still running.
379436
}
380-
}
381-
382-
if (!ctagsRunning) {
437+
} else {
383438
initialize();
384439
}
385440

386-
if (file.length() < 1 || "\n".equals(file)) return null;
387-
388441
synchronized(syncRoot) {
389442
signalled = true;
390443
syncRoot.notify();
391444
}
392445

393446
Thread clientThread = Thread.currentThread();
394447
ScheduledFuture futureTimeout = schedExecutor.schedule(() -> {
395-
LOGGER.log(Level.WARNING, "Ctags futureTimeout executing.");
448+
LOGGER.log(Level.FINE, "Ctags futureTimeout executing re t{0}.",
449+
clientThread.getId());
396450
clientThread.interrupt();
397451
outThread.interrupt();
398452
ctags.destroyForcibly();
453+
LOGGER.log(Level.FINE, "Ctags futureTimeout executed.");
399454
}, CTAGSWAIT_S, TimeUnit.SECONDS);
400455

401456
Definitions ret;
@@ -545,9 +600,6 @@ private void outThreadStart() {
545600
CtagsReader rdr = new CtagsReader();
546601
try {
547602
readTags(rdr);
548-
if (Thread.interrupted()) {
549-
throw new InterruptedException("readTags()");
550-
}
551603
Definitions defs = rdr.getDefinitions();
552604
buffer.put(defs);
553605
} catch (InterruptedException ex) {

src/org/opensolaris/opengrok/index/IndexDatabase.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,9 @@ private void indexParallel(IndexDownArgs args) throws IOException {
10151015
x.exception = e;
10161016
ret = false;
10171017
} catch (RuntimeException|IOException e) {
1018+
String errmsg = String.format("ERROR addFile(): %s",
1019+
x.file);
1020+
LOGGER.log(Level.WARNING, errmsg, e);
10181021
x.exception = e;
10191022
ret = false;
10201023
} finally {

0 commit comments

Comments
 (0)