Skip to content

Commit 46191d5

Browse files
committed
SwingConsolePane: detect infinite loops
When initializing, we invoke `new ConsolePanel`. If that invocation triggers more output to the console, we will get stuck in a loop. Let's be proactive about that situation and fail fast instead. This can happen e.g. if the chosen LookAndFeel is broken due to missing or inaccessible ComponentUI classes.
1 parent 12f9041 commit 46191d5

File tree

1 file changed

+52
-3
lines changed

1 file changed

+52
-3
lines changed

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

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public class SwingConsolePane extends AbstractConsolePane<JPanel> {
8989

9090
private JPanel component;
9191

92+
private boolean initializing;
93+
9294
public SwingConsolePane(final Context context) {
9395
super(context);
9496
}
@@ -127,7 +129,7 @@ public void run() {
127129

128130
@Override
129131
public JPanel getComponent() {
130-
if (consolePanel == null) initLoggingPanel();
132+
if (consolePanel == null) initComponents();
131133
return component;
132134
}
133135

@@ -139,12 +141,58 @@ public Class<JPanel> getComponentType() {
139141
// -- Helper methods - lazy initialization --
140142

141143
private ConsolePanel consolePanel() {
142-
if (consolePanel == null) initLoggingPanel();
144+
if (consolePanel == null) initComponents();
143145
return consolePanel;
144146
}
145147

146-
private synchronized void initLoggingPanel() {
148+
private synchronized void initComponents() {
147149
if (consolePanel != null) return;
150+
if (initializing) {
151+
// NB: We are in a loop, with `new ConsolePanel` triggering more output.
152+
//
153+
// In a nutshell:
154+
// stderr -> ConsoleService -> new ConsolePanel -> stderr -> ...
155+
//
156+
// Here is an example where we have experienced this happening:
157+
//
158+
// ...
159+
// at org.scijava.ui.swing.console.ConsolePanel.<init>(...)
160+
// at org.scijava.ui.swing.console.SwingConsolePane.initComponents(...)
161+
// at org.scijava.ui.swing.console.SwingConsolePane.consolePanel(...)
162+
// at org.scijava.ui.swing.console.SwingConsolePane.append(...)
163+
// at org.scijava.ui.console.AbstractConsolePane.outputOccurred(...)
164+
// at org.scijava.console.DefaultConsoleService.notifyListeners(...)
165+
// at org.scijava.console.DefaultConsoleService$OutputStreamReporter.publish(...)
166+
// at org.scijava.console.DefaultConsoleService$OutputStreamReporter.write(...)
167+
// at org.scijava.console.MultiOutputStream.write(...)
168+
// at java.io.PrintStream.write(...)
169+
// ...
170+
// at java.lang.Throwable.printStackTrace(...)
171+
// at javax.swing.UIDefaults.getUIError(...)
172+
// at javax.swing.MultiUIDefaults.getUIError(...)
173+
// at javax.swing.UIDefaults.getUI(...)
174+
// at javax.swing.UIManager.getUI(...)
175+
// at javax.swing.text.JTextComponent.updateUI(...)
176+
// at javax.swing.text.JTextComponent.<init>(...)
177+
// at javax.swing.JEditorPane.<init>(...)
178+
// at javax.swing.JTextPane.<init>(...)
179+
// at org.scijava.ui.swing.console.ConsolePanel.initGui(...)
180+
// at org.scijava.ui.swing.console.ConsolePanel.<init>(...)
181+
// at org.scijava.ui.swing.console.SwingConsolePane.initComponents(...)
182+
// at org.scijava.ui.swing.console.SwingConsolePane.consolePanel(...)
183+
// at org.scijava.ui.swing.console.SwingConsolePane.append(...)
184+
// at org.scijava.ui.console.AbstractConsolePane.outputOccurred(...)
185+
// at org.scijava.console.DefaultConsoleService.notifyListeners(...)
186+
// at org.scijava.console.DefaultConsoleService$OutputStreamReporter.publish(...)
187+
// at org.scijava.console.DefaultConsoleService$OutputStreamReporter.write(...)
188+
// at org.scijava.console.MultiOutputStream.write(...)
189+
// at java.io.PrintStream.write(...)
190+
// ...
191+
//
192+
throw new RuntimeException(
193+
"Output loop while initializing the console GUI.");
194+
}
195+
initializing = true;
148196
consolePanel = new ConsolePanel(context);
149197
loggingPanel = new LoggingPanel(context, LOG_FORMATTING_SETTINGS_KEY);
150198
logService.addLogListener(loggingPanel);
@@ -153,6 +201,7 @@ private synchronized void initLoggingPanel() {
153201
tabs.addTab("Console", consolePanel);
154202
tabs.addTab("Log", loggingPanel);
155203
component.add(tabs, "grow");
204+
initializing = false;
156205
}
157206

158207
// -- Helper methods - testing --

0 commit comments

Comments
 (0)