I have code which detects if the Java AWT event queue is frozen (busy processing some event or waiting for a lock) for an excessive amount of time, due to buggy foreign code which fails to use SwingWorker
or similar, and I want to offer to recover. Just killing the event dispatch thread with Thread.stop
works, but it could be hazardous (and EQ might be blocked simply because the computer is temporarily overloaded), so I would prefer to prompt the user for confirmation. However displaying a dialog requires waiting for EQ to be unblocked, which is exactly what is not possible.
Is there any way, from a reasonably portable Java program, to display a dialog (or really any kind of UI element which can respond to input events) without involving the normal event queue?
I already tried running SystemTray.add(TrayIcon)
, which does manage to display a tray item when called from another thread... but the icon is not painted, and ActionEvent
s are not delivered, so this is not helpful.
It also seems possible to display a new JFrame
and even paint a label on it using paintImmediately
, but there is still no apparent way to receive mouse events.
Following Tom Hawtin's hint, I tried to create a new AppContext
. On JDK 6u18, a dialog shown in the new context seems to paint correctly but does not receive mouse events until the main event queue is unblocked, defeating the purpose:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import sun.awt.AppContext;
import sun.awt.SunToolkit;
public class Main {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public @Override void run() {
new MainWindow().setVisible(true);
System.err.println("main context: " + AppContext.getAppContext());
}
});
new TrackEQ(1000*3);
}
private static class MainWindow extends JFrame {
MainWindow() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JButton pause = new JButton("Pause");
pause.addActionListener(new ActionListener() {
public @Override void actionPerformed(ActionEvent e) {
try {
Thread.sleep(15000);
} catch (InterruptedException x) {
x.printStackTrace();
}
}
});
getContentPane().add(pause);
pack();
setLocation(100, 100);
}
}
private static class TrackEQ implements Runnable {
private final ScheduledExecutorService svc;
private final int timeout;
private boolean stuck = false;
private boolean wait = false;
private Thread eq;
TrackEQ(int timeout) {
this.timeout = timeout;
svc = Executors.newSingleThreadScheduledExecutor();
svc.schedule(this, 0, TimeUnit.MILLISECONDS);
}
public @Override synchronized void run() {
if (EventQueue.isDispatchThread()) {
stuck = false;
eq = Thread.currentThread();
} else {
if (stuck && !wait) {
System.err.println("UI is stuck!");
wait = true;
Map<Thread,StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
StackTraceElement[] stack = stackTraces.get(eq);
if (stack != null) {
for (StackTraceElement el : stack) {
System.err.println("stuck at " + el);
}
ThreadGroup grp = new ThreadGroup("showing dialog");
grp.setDaemon(true);
new Thread(grp, new Runnable() {
public @Override void run() {
System.err.println("created new app context in " + Thread.currentThread().getThreadGroup());
SunToolkit.createNewAppContext();
EventQueue.invokeLater(new Runnable() {
public @Override void run() {
System.err.println("main EQ=" + eq + " whereas my EQ=" + Thread.currentThread());
System.err.println("will show dialog in " + AppContext.getAppContext());
final JDialog dlg = new JDialog();
dlg.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JButton fix = new JButton("Fix!");
fix.addActionListener(new ActionListener() {
@SuppressWarnings("deprecation")
public @Override void actionPerformed(ActionEvent e) {
System.err.println("agreed to fix");
eq.stop();
wait = false;
dlg.setVisible(false);
}
});
dlg.getContentPane().add(fix);
dlg.pack();
dlg.setLocation(200, 100);
dlg.setVisible(true);
System.err.println("showed dialog");
}
});
}
}, "showing dialog").start();
} else {
System.err.println("no stack trace for " + eq + "; listed threads: " + stackTraces.keySet());
}
} else {
stuck = true;
}
EventQueue.invokeLater(this);
svc.schedule(this, timeout, TimeUnit.MILLISECONDS);
}
}
}
private Main() {}
}