A while back we added some code to our application to detect and attempt to recover from a Swing EDT deadlock, so the user could at least save their files (it would be best to not have a deadlock, but...). In Java 1.6, this is easy. Detect that the EDT has been blocked for a sufficient amount of time, and then call this from a background thread:
EventQueue newQ = new EventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(newQ);
New UI events will be processed on a new EventQueue/EDT, and the user can save their work.
In Java 8, this does not work because the implementation of EventQueue.push has been changed to copy the (blocked) EventDispatchThread from the old queue to a new one.
Of course, I can always do something a little evil:
private static void hackAroundJava8Protections(EventQueue newQ) {
try {
Field field = newQ.getClass().getDeclaredField("dispatchThread");
field.setAccessible(true);
field.set(newQ, null);
Method method = newQ.getClass().getDeclaredMethod("initDispatchThread");
method.setAccessible(true);
method.invoke(newQ);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
This starts a new EventDispatchThread, allowing use of the application UI. I was able to save data as if I were a user. I'm not sure of what downsides there might be. Maybe there's a less scary way of restarting a blocked EDT?
try {
Field field = EventQueue.class.getDeclaredField("dispatchThread");
field.setAccessible(true);
Thread dispatchThread = (Thread) field.get(systemEventQueue);
field.set(systemEventQueue, null);
dispatchThread.stop();
} catch (Exception e) {
e.printStackTrace();
}
It's still not very good, but it works.
initDispatchThread
does not need to be manually invoked, as the EventQueue does this automatically when dispatchThread
is null.
If the old thread is not stopped and it unblocks later, everything goes crazy. I suppose then we have two threads processing the Queue and nothing here is build for thread-safety thus it all breaks down.
I'm still looking for a better solution to do this.
Another idea i had is to create my own EventQueue and replace the original one with it using EventQueue.push(newQueue)
, but looking into the code of EventQueue it can be extended, but not modified in the way neccessary. And rewriting it looks also problematic to me - there is a lot of sophisticated stuff happening there, which i don't want to mess with.
// run this when program starts to identify and remember the initial awtEventThread
Thread awtEventThread;
// identify the original thread:
EventQueue.invokeLater(() -> awtEventThread = Thread.currentThread());
// run this when a reset is neccessary:
EventQueue systemEventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); // the currently active Queue
EventQueue newEventQueue1 = new EventQueue(); // helper Queue to create a new AWT-Event-Threads
newEventQueue1.postEvent(new InvocationEvent(this, () -> {})); // init new AWT-Event-Thread - it happens automatically when an event is posted
EventQueue newEventQueue2 = new EventQueue(); // the new queue we want to use
systemEventQueue.push(newEventQueue2); // copy thread & events from systemEventQueue
newEventQueue1.push(newEventQueue2); // copy thread & (no) events from newEventQueue1 *HACK*
awtEventThread.stop(); // stop the old thread to prevent two threads processing the Queue - would get MESSY
EventQueue.invokeLater(() -> awtEventThread = Thread.currentThread()); // update our awtEventThread variable for the next time
This solution is not very beautiful, but it works. And it works without reflection and setAccessible(true)
.
I use one immplementation detail of the push()
method to copy the newly created Thread from newEventQueue1
to newEventQueue2
, which inherited everything from the original systemEventQueue
.
After the new Thread is started and the Queue is set up the old thread NEEEDS to be terminated. If not so - in case it unblocks it will continue to process the queue and then it gets messy. The system is not ready to be processed by two threads in parallel.