Skip to content

Commit 881065a

Browse files
committed
SwingConsolePane: first cut at batching the output
We want to avoid an excessive amount of queuing to the EDT via EventQueue.invokeLater calls. This change puts all output events into a queue, which is fully drained by each Runnable queued up on the EDT. Concurrent appends push the new output events into the queue, but only queue up a new Runnable on the EDT if there isn't one already running. Concurrency issues are always tricky, but I _think_ this logic is thread-safe. It is OK that an extra Runnable or two might get queued up on the EDT. The danger is the inverse situation: that the append method might opt not to queue up a Runnable, even though the currently active Runnable on the EDT is already winding down. If that happened, the appearance of some output might be delayed by an arbitrarily lengthy period, until the next bit of output rolls around... In the case of SwingConsolePaneBenchmark, this change reduces the number of threadService.queue calls from >2000 to a mere _2_ calls! However, the improvement in real time is not what you might expect. There are three scenarios: * BATCHED console updates (i.e., with this commit applied). * THOROUGH console updates, queuing to the EDT on every output event. (i.e., without this commit applied). * NAIVE console updates, without queuing anything to the EDT at all. (i.e., w/o this commit or d86b605). I ran three trials of SwingConsolePaneBenchmark for each of the scenarios above. Here are the results (in ms) on my machine: * BATCHED = 158 159 154 * THOROUGH = 169 176 164 * NAIVE = 999 716 735 (!!) In all three cases, all output does eventually make it to the console, and all the assertions in SwingConsolePaneBenchmark actually pass. So at least on my system (OS X with Apple Java 1.6.0_65), it is not a matter of correctness, only performance. There is a downside to behavior with the BATCHED console updates though: if the number of output events flood the queue faster than it can be drained on the EDT, then the EDT will lock up indefinitely -- whereas with the THOROUGH approach, at least you'll continue to see output on the console, since the EDT gets freed up afterwards every time.
1 parent f2ae3fb commit 881065a

File tree

1 file changed

+26
-8
lines changed

1 file changed

+26
-8
lines changed

src/main/java/org/scijava/ui/swing/console/SwingConsolePane.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.awt.Component;
3636
import java.awt.Dimension;
3737
import java.awt.Font;
38+
import java.util.concurrent.ConcurrentLinkedQueue;
3839

3940
import javax.swing.JPanel;
4041
import javax.swing.JScrollPane;
@@ -81,6 +82,13 @@ public class SwingConsolePane extends AbstractConsolePane<JPanel> {
8182
*/
8283
private Component window;
8384

85+
/** Output queue, to avoid excessive queuing to the EDT. */
86+
private ConcurrentLinkedQueue<OutputEvent> outputQueue =
87+
new ConcurrentLinkedQueue<OutputEvent>();
88+
89+
/** Flag indicating that the EDT is currently flushing the output. */
90+
private boolean outputFlushing;
91+
8492
public SwingConsolePane(final Context context) {
8593
super(context);
8694
}
@@ -107,19 +115,29 @@ public JScrollPane getScrollPane() {
107115
@Override
108116
public void append(final OutputEvent event) {
109117
if (consolePanel == null) initConsolePanel();
118+
outputQueue.add(event);
119+
if (outputFlushing) return;
120+
outputFlushing = true;
121+
110122
threadService.queue(new Runnable() {
111123

112124
@Override
113125
public void run() {
114-
final boolean atBottom =
115-
StaticSwingUtils.isScrolledToBottom(scrollPane);
116-
try {
117-
doc.insertString(doc.getLength(), event.getOutput(), getStyle(event));
118-
}
119-
catch (final BadLocationException exc) {
120-
throw new RuntimeException(exc);
126+
while (true) {
127+
outputFlushing = !outputQueue.isEmpty();
128+
final OutputEvent item = outputQueue.poll();
129+
if (item == null) break;
130+
131+
final boolean atBottom =
132+
StaticSwingUtils.isScrolledToBottom(scrollPane);
133+
try {
134+
doc.insertString(doc.getLength(), item.getOutput(), getStyle(item));
135+
}
136+
catch (final BadLocationException exc) {
137+
throw new RuntimeException(exc);
138+
}
139+
if (atBottom) StaticSwingUtils.scrollToBottom(scrollPane);
121140
}
122-
if (atBottom) StaticSwingUtils.scrollToBottom(scrollPane);
123141
}
124142
});
125143
}

0 commit comments

Comments
 (0)