Skip to content

Commit 282a683

Browse files
committed
Read ctags output from a separate thread
tuxillo's testing showed that some Ctags instances seemed to hang upon writing to their stdin. java.lang.Process documentation advises that "failure to promptly ... read the output stream of the subprocess may cause [it] ... to block, or even deadlock." Using another thread can help to mitigate the possible deadlock.
1 parent d08f9d8 commit 282a683

File tree

2 files changed

+119
-5
lines changed

2 files changed

+119
-5
lines changed

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

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,15 @@
4646
public class Ctags {
4747

4848
private static final Logger LOGGER = LoggerFactory.getLogger(Ctags.class);
49+
private static final long SIGNALWAIT_MS = 5000;
4950

51+
private final CtagsBuffer buffer = new CtagsBuffer();
52+
private final Object syncRoot = new Object();
53+
private volatile boolean closing;
54+
private volatile boolean signalled;
5055
private Process ctags;
56+
Thread errThread;
57+
Thread outThread;
5158
private OutputStreamWriter ctagsIn;
5259
private BufferedReader ctagsOut;
5360
private static final String CTAGS_FILTER_TERMINATOR = "__ctags_done_with_file__";
@@ -77,6 +84,7 @@ public void setCTagsExtraOptionsFile(String CTagsExtraOptionsFile) {
7784
public void close() throws IOException {
7885
IOUtils.close(ctagsIn);
7986
if (ctags != null) {
87+
closing = true;
8088
LOGGER.log(Level.FINE, "Destroying ctags command");
8189
ctags.destroy();
8290
}
@@ -292,7 +300,7 @@ private void initialize() throws IOException {
292300
ctagsIn = new OutputStreamWriter(ctags.getOutputStream());
293301
ctagsOut = new BufferedReader(new InputStreamReader(ctags.getInputStream()));
294302

295-
Thread errThread = new Thread(new Runnable() {
303+
errThread = new Thread(new Runnable() {
296304

297305
@Override
298306
public void run() {
@@ -303,6 +311,7 @@ public void run() {
303311
while ((s = error.readLine()) != null) {
304312
sb.append(s);
305313
sb.append('\n');
314+
if (closing) break;
306315
}
307316
} catch (IOException exp) {
308317
LOGGER.log(Level.WARNING, "Got an exception reading ctags error stream: ", exp);
@@ -314,6 +323,34 @@ public void run() {
314323
});
315324
errThread.setDaemon(true);
316325
errThread.start();
326+
327+
outThread = new Thread(() -> {
328+
while (!closing) {
329+
synchronized(syncRoot) {
330+
while (!signalled && !closing) {
331+
try {
332+
syncRoot.wait(SIGNALWAIT_MS);
333+
} catch (InterruptedException e) {
334+
LOGGER.log(Level.WARNING,
335+
"Ctags direct client unexpectedly interrupted");
336+
}
337+
}
338+
signalled = false;
339+
}
340+
if (closing) return;
341+
342+
CtagsReader rdr = new CtagsReader();
343+
readTags(rdr);
344+
Definitions defs = rdr.getDefinitions();
345+
try {
346+
buffer.put(defs);
347+
} catch (InterruptedException ex) {
348+
// ignore
349+
}
350+
}
351+
});
352+
outThread.setDaemon(true);
353+
outThread.start();
317354
}
318355

319356
public Definitions doCtags(String file) throws IOException {
@@ -338,12 +375,19 @@ public Definitions doCtags(String file) throws IOException {
338375

339376
Definitions ret = null;
340377
if (file.length() > 0 && !"\n".equals(file)) {
341-
//log.fine("doing >" + file + "<");
378+
synchronized(syncRoot) {
379+
signalled = true;
380+
syncRoot.notify();
381+
}
382+
342383
ctagsIn.write(file);
343384
ctagsIn.flush();
344-
CtagsReader rdr = new CtagsReader();
345-
readTags(rdr);
346-
ret = rdr.getDefinitions();
385+
try {
386+
ret = buffer.take();
387+
} catch (InterruptedException e) {
388+
LOGGER.log(Level.WARNING,
389+
"Ctags indirect client unexpectedly interrupted");
390+
}
347391
}
348392

349393
return ret;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* CDDL HEADER START
3+
*
4+
* The contents of this file are subject to the terms of the
5+
* Common Development and Distribution License (the "License").
6+
* You may not use this file except in compliance with the License.
7+
*
8+
* See LICENSE.txt included in this distribution for the specific
9+
* language governing permissions and limitations under the License.
10+
*
11+
* When distributing Covered Code, include this CDDL HEADER in each
12+
* file and include the License file at LICENSE.txt.
13+
* If applicable, add the following below this CDDL HEADER, with the
14+
* fields enclosed by brackets "[]" replaced with your own identifying
15+
* information: Portions Copyright [yyyy] [name of copyright owner]
16+
*
17+
* CDDL HEADER END
18+
*/
19+
20+
/*
21+
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
22+
* Portions Copyright (c) 2017, Chris Fraire <[email protected]>.
23+
*/
24+
25+
package org.opensolaris.opengrok.analysis;
26+
27+
import java.util.concurrent.locks.Condition;
28+
import java.util.concurrent.locks.Lock;
29+
import java.util.concurrent.locks.ReentrantLock;
30+
31+
/**
32+
* Represents a blocking buffer to allow threads marshaling to and from a
33+
* {@link Ctags} process to hand off results in a synchronized manner.
34+
*/
35+
public class CtagsBuffer {
36+
private final Lock lock = new ReentrantLock();
37+
private final Condition notFull = lock.newCondition();
38+
private final Condition notEmpty = lock.newCondition();
39+
40+
// A buffer of length=1 is all that's needed.
41+
private final Definitions[] items = new Definitions[1];
42+
private volatile int putptr, takeptr, count;
43+
44+
public void put(Definitions defs) throws InterruptedException {
45+
lock.lock();
46+
try {
47+
while (count == items.length) notFull.await();
48+
items[putptr] = defs;
49+
if (++putptr == items.length) putptr = 0;
50+
++count;
51+
notEmpty.signal();
52+
} finally {
53+
lock.unlock();
54+
}
55+
}
56+
57+
public Definitions take() throws InterruptedException {
58+
lock.lock();
59+
try {
60+
while (count == 0) notEmpty.await();
61+
Definitions defs = items[takeptr];
62+
if (++takeptr == items.length) takeptr = 0;
63+
--count;
64+
notFull.signal();
65+
return defs;
66+
} finally {
67+
lock.unlock();
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)