4913324: Deadlock when using two event queues

Reviewed-by: anthony, ant, dcherepanov
This commit is contained in:
Artem Ananiev 2009-11-27 15:26:07 +03:00
parent 3336503bbb
commit a6dd224efd
6 changed files with 358 additions and 164 deletions

View file

@ -104,11 +104,8 @@ class EventDispatchThread extends Thread {
} else {
stopEvent.dispatch();
}
synchronized (theQueue) {
if (theQueue.getDispatchThread() == this) {
theQueue.detachDispatchThread();
}
}
theQueue.detachDispatchThread(this, false);
}
public void stopDispatching() {
@ -142,35 +139,7 @@ class EventDispatchThread extends Thread {
}
});
} finally {
/*
* This synchronized block is to secure that the event dispatch
* thread won't die in the middle of posting a new event to the
* associated event queue. It is important because we notify
* that the event dispatch thread is busy after posting a new event
* to its queue, so the EventQueue.dispatchThread reference must
* be valid at that point.
*/
synchronized (theQueue) {
if (theQueue.getDispatchThread() == this) {
theQueue.detachDispatchThread();
}
/*
* Event dispatch thread dies in case of an uncaught exception.
* A new event dispatch thread for this queue will be started
* only if a new event is posted to it. In case if no more
* events are posted after this thread died all events that
* currently are in the queue will never be dispatched.
*/
/*
* Fix for 4648733. Check both the associated java event
* queue and the PostEventQueue.
*/
if (theQueue.peekEvent() != null ||
!SunToolkit.isPostEventQueueEmpty()) {
theQueue.initDispatchThread();
}
AWTAutoShutdown.getInstance().notifyThreadFree(this);
}
theQueue.detachDispatchThread(this, true);
}
}

View file

@ -45,6 +45,9 @@ import sun.awt.SunToolkit;
import sun.awt.EventQueueItem;
import sun.awt.AWTAccessor;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* <code>EventQueue</code> is a platform-independent class
* that queues events, both from the underlying peer classes
@ -127,6 +130,14 @@ public class EventQueue {
*/
private EventQueue previousQueue;
/*
* A single lock to synchronize the push()/pop() and related operations with
* all the EventQueues from the AppContext. Synchronization on any particular
* event queue(s) is not enough: we should lock the whole stack.
*/
private final Lock pushPopLock;
private final Condition pushPopCond;
private EventDispatchThread dispatchThread;
private final ThreadGroup threadGroup =
@ -158,11 +169,11 @@ public class EventQueue {
static {
AWTAccessor.setEventQueueAccessor(
new AWTAccessor.EventQueueAccessor() {
public EventQueue getNextQueue(EventQueue eventQueue) {
return eventQueue.nextQueue;
}
public Thread getDispatchThread(EventQueue eventQueue) {
return eventQueue.dispatchThread;
return eventQueue.getDispatchThread();
}
public boolean isDispatchThreadImpl(EventQueue eventQueue) {
return eventQueue.isDispatchThreadImpl();
}
});
}
@ -179,6 +190,9 @@ public class EventQueue {
* may call AppContext.getAppContext() before createNewAppContext()
* completes thus causing mess in thread group to appcontext mapping.
*/
pushPopLock = (Lock)AppContext.getAppContext().get(AppContext.EVENT_QUEUE_LOCK_KEY);
pushPopCond = (Condition)AppContext.getAppContext().get(AppContext.EVENT_QUEUE_COND_KEY);
}
/**
@ -207,7 +221,8 @@ public class EventQueue {
*/
final void postEventPrivate(AWTEvent theEvent) {
theEvent.isPosted = true;
synchronized(this) {
pushPopLock.lock();
try {
if (dispatchThread == null && nextQueue == null) {
if (theEvent.getSource() == AWTAutoShutdown.getInstance()) {
return;
@ -221,6 +236,8 @@ public class EventQueue {
return;
}
postEvent(theEvent, getPriority(theEvent));
} finally {
pushPopLock.unlock();
}
}
@ -280,9 +297,9 @@ public class EventQueue {
if (theEvent.getSource() != AWTAutoShutdown.getInstance()) {
AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
}
notifyAll();
pushPopCond.signalAll();
} else if (notifyID) {
notifyAll();
pushPopCond.signalAll();
}
} else {
// The event was not coalesced or has non-Component source.
@ -290,7 +307,7 @@ public class EventQueue {
queues[priority].tail.next = newItem;
queues[priority].tail = newItem;
if (notifyID) {
notifyAll();
pushPopCond.signalAll();
}
}
}
@ -482,7 +499,8 @@ public class EventQueue {
* event queues are nested with push()/pop().
*/
SunToolkit.flushPendingEvents();
synchronized (this) {
pushPopLock.lock();
try {
for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
if (queues[i].head != null) {
EventQueueItem entry = queues[i].head;
@ -495,7 +513,9 @@ public class EventQueue {
}
}
AWTAutoShutdown.getInstance().notifyThreadFree(dispatchThread);
wait();
pushPopCond.await();
} finally {
pushPopLock.unlock();
}
} while(true);
}
@ -508,7 +528,8 @@ public class EventQueue {
* event queues are nested with push()/pop().
*/
SunToolkit.flushPendingEvents();
synchronized (this) {
pushPopLock.lock();
try {
for (int i = 0; i < NUM_PRIORITIES; i++) {
for (EventQueueItem entry = queues[i].head, prev = null;
entry != null; prev = entry, entry = entry.next)
@ -527,9 +548,11 @@ public class EventQueue {
}
}
}
this.waitForID = id;
wait();
this.waitForID = 0;
waitForID = id;
pushPopCond.await();
waitForID = 0;
} finally {
pushPopLock.unlock();
}
} while(true);
}
@ -539,11 +562,16 @@ public class EventQueue {
* without removing it.
* @return the first event
*/
public synchronized AWTEvent peekEvent() {
for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
if (queues[i].head != null) {
return queues[i].head.event;
public AWTEvent peekEvent() {
pushPopLock.lock();
try {
for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
if (queues[i].head != null) {
return queues[i].head.event;
}
}
} finally {
pushPopLock.unlock();
}
return null;
@ -555,14 +583,19 @@ public class EventQueue {
* @return the first event of the specified id or <code>null</code>
* if there is no such event
*/
public synchronized AWTEvent peekEvent(int id) {
for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
EventQueueItem q = queues[i].head;
for (; q != null; q = q.next) {
if (q.event.getID() == id) {
return q.event;
public AWTEvent peekEvent(int id) {
pushPopLock.lock();
try {
for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
EventQueueItem q = queues[i].head;
for (; q != null; q = q.next) {
if (q.event.getID() == id) {
return q.event;
}
}
}
} finally {
pushPopLock.unlock();
}
return null;
@ -661,17 +694,27 @@ public class EventQueue {
public static long getMostRecentEventTime() {
return Toolkit.getEventQueue().getMostRecentEventTimeImpl();
}
private synchronized long getMostRecentEventTimeImpl() {
return (Thread.currentThread() == dispatchThread)
? mostRecentEventTime
: System.currentTimeMillis();
private long getMostRecentEventTimeImpl() {
pushPopLock.lock();
try {
return (Thread.currentThread() == dispatchThread)
? mostRecentEventTime
: System.currentTimeMillis();
} finally {
pushPopLock.unlock();
}
}
/**
* @return most recent event time on all threads.
*/
synchronized long getMostRecentEventTimeEx() {
return mostRecentEventTime;
long getMostRecentEventTimeEx() {
pushPopLock.lock();
try {
return mostRecentEventTime;
} finally {
pushPopLock.unlock();
}
}
/**
@ -689,10 +732,15 @@ public class EventQueue {
public static AWTEvent getCurrentEvent() {
return Toolkit.getEventQueue().getCurrentEventImpl();
}
private synchronized AWTEvent getCurrentEventImpl() {
return (Thread.currentThread() == dispatchThread)
? ((AWTEvent)currentEvent.get())
: null;
private AWTEvent getCurrentEventImpl() {
pushPopLock.lock();
try {
return (Thread.currentThread() == dispatchThread)
? ((AWTEvent)currentEvent.get())
: null;
} finally {
pushPopLock.unlock();
}
}
/**
@ -706,21 +754,22 @@ public class EventQueue {
* @throws NullPointerException if <code>newEventQueue</code> is <code>null</code>
* @since 1.2
*/
public synchronized void push(EventQueue newEventQueue) {
public void push(EventQueue newEventQueue) {
if (eventLog.isLoggable(PlatformLogger.FINE)) {
eventLog.fine("EventQueue.push(" + newEventQueue + ")");
}
if (nextQueue != null) {
nextQueue.push(newEventQueue);
return;
}
pushPopLock.lock();
try {
EventQueue toPush = this;
while (toPush.nextQueue != null) {
toPush = toPush.nextQueue;
}
synchronized (newEventQueue) {
// Transfer all events forward to new EventQueue.
while (peekEvent() != null) {
while (toPush.peekEvent() != null) {
try {
newEventQueue.postEventPrivate(getNextEvent());
newEventQueue.postEventPrivate(toPush.getNextEvent());
} catch (InterruptedException ie) {
if (eventLog.isLoggable(PlatformLogger.FINE)) {
eventLog.fine("Interrupted push", ie);
@ -728,27 +777,30 @@ public class EventQueue {
}
}
newEventQueue.previousQueue = this;
}
/*
* Stop the event dispatch thread associated with the currently
* active event queue, so that after the new queue is pushed
* on the top this event dispatch thread won't prevent AWT from
* being automatically shut down.
* Use stopDispatchingLater() to avoid deadlock: stopDispatching()
* waits for the dispatch thread to exit, so if the dispatch
* thread attempts to synchronize on this EventQueue object
* it will never exit since we already hold this lock.
*/
if (dispatchThread != null) {
dispatchThread.stopDispatchingLater();
}
newEventQueue.previousQueue = toPush;
nextQueue = newEventQueue;
/*
* Stop the event dispatch thread associated with the currently
* active event queue, so that after the new queue is pushed
* on the top this event dispatch thread won't prevent AWT from
* being automatically shut down.
* Use stopDispatchingLater() to avoid deadlock: stopDispatching()
* waits for the dispatch thread to exit, which in turn waits
* for the lock in EQ.detachDispatchThread(), which is hold by
* this method.
*/
if (toPush.dispatchThread != null) {
toPush.dispatchThread.stopDispatchingLater();
}
AppContext appContext = AppContext.getAppContext();
if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) {
appContext.put(AppContext.EVENT_QUEUE_KEY, newEventQueue);
toPush.nextQueue = newEventQueue;
AppContext appContext = AppContext.getAppContext();
if (appContext.get(AppContext.EVENT_QUEUE_KEY) == toPush) {
appContext.put(AppContext.EVENT_QUEUE_KEY, newEventQueue);
}
} finally {
pushPopLock.unlock();
}
}
@ -770,25 +822,24 @@ public class EventQueue {
eventLog.fine("EventQueue.pop(" + this + ")");
}
// To prevent deadlock, we lock on the previous EventQueue before
// this one. This uses the same locking order as everything else
// in EventQueue.java, so deadlock isn't possible.
EventQueue prev = previousQueue;
synchronized ((prev != null) ? prev : this) {
synchronized(this) {
if (nextQueue != null) {
nextQueue.pop();
return;
EventDispatchThread dt = null;
pushPopLock.lock();
try {
EventQueue toPop = this;
while (toPop.nextQueue != null) {
toPop = toPop.nextQueue;
}
if (previousQueue == null) {
EventQueue prev = toPop.previousQueue;
if (prev == null) {
throw new EmptyStackException();
}
toPop.previousQueue = null;
// Transfer all events back to previous EventQueue.
previousQueue.nextQueue = null;
while (peekEvent() != null) {
prev.nextQueue = null;
while (toPop.peekEvent() != null) {
try {
previousQueue.postEventPrivate(getNextEvent());
prev.postEventPrivate(toPop.getNextEvent());
} catch (InterruptedException ie) {
if (eventLog.isLoggable(PlatformLogger.FINE)) {
eventLog.fine("Interrupted pop", ie);
@ -797,14 +848,14 @@ public class EventQueue {
}
AppContext appContext = AppContext.getAppContext();
if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) {
appContext.put(AppContext.EVENT_QUEUE_KEY, previousQueue);
appContext.put(AppContext.EVENT_QUEUE_KEY, prev);
}
previousQueue = null;
}
dt = toPop.dispatchThread;
} finally {
pushPopLock.unlock();
}
EventDispatchThread dt = this.dispatchThread;
if (dt != null) {
dt.stopDispatching(); // Must be done outside synchronized
// block to avoid possible deadlock
@ -833,16 +884,27 @@ public class EventQueue {
*/
public static boolean isDispatchThread() {
EventQueue eq = Toolkit.getEventQueue();
EventQueue next = eq.nextQueue;
while (next != null) {
eq = next;
next = eq.nextQueue;
return eq.isDispatchThreadImpl();
}
final boolean isDispatchThreadImpl() {
EventQueue eq = this;
pushPopLock.lock();
try {
EventQueue next = eq.nextQueue;
while (next != null) {
eq = next;
next = eq.nextQueue;
}
return (Thread.currentThread() == eq.dispatchThread);
} finally {
pushPopLock.unlock();
}
return (Thread.currentThread() == eq.dispatchThread);
}
final void initDispatchThread() {
synchronized (this) {
pushPopLock.lock();
try {
AppContext appContext = AppContext.getAppContext();
if (dispatchThread == null && !threadGroup.isDestroyed() && !appContext.isDisposed()) {
dispatchThread = (EventDispatchThread)
@ -861,11 +923,45 @@ public class EventQueue {
AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
dispatchThread.start();
}
} finally {
pushPopLock.unlock();
}
}
final void detachDispatchThread() {
dispatchThread = null;
final void detachDispatchThread(EventDispatchThread edt, boolean restart) {
/*
* This synchronized block is to secure that the event dispatch
* thread won't die in the middle of posting a new event to the
* associated event queue. It is important because we notify
* that the event dispatch thread is busy after posting a new event
* to its queue, so the EventQueue.dispatchThread reference must
* be valid at that point.
*/
pushPopLock.lock();
try {
EventDispatchThread oldDispatchThread = dispatchThread;
if (dispatchThread == edt) {
dispatchThread = null;
}
if (restart) {
/*
* Event dispatch thread dies in case of an uncaught exception.
* A new event dispatch thread for this queue will be started
* only if a new event is posted to it. In case if no more
* events are posted after this thread died all events that
* currently are in the queue will never be dispatched.
*
* Fix for 4648733. Check both the associated java event
* queue and the PostEventQueue.
*/
if ((peekEvent() != null) || !SunToolkit.isPostEventQueueEmpty()) {
initDispatchThread();
}
AWTAutoShutdown.getInstance().notifyThreadFree(oldDispatchThread);
}
} finally {
pushPopLock.unlock();
}
}
/*
@ -878,7 +974,12 @@ public class EventQueue {
* @see java.awt.EventQueue#detachDispatchThread
*/
final EventDispatchThread getDispatchThread() {
return dispatchThread;
pushPopLock.lock();
try {
return dispatchThread;
} finally {
pushPopLock.unlock();
}
}
/*
@ -895,7 +996,8 @@ public class EventQueue {
*/
final void removeSourceEvents(Object source, boolean removeAllEvents) {
SunToolkit.flushPendingEvents();
synchronized (this) {
pushPopLock.lock();
try {
for (int i = 0; i < NUM_PRIORITIES; i++) {
EventQueueItem entry = queues[i].head;
EventQueueItem prev = null;
@ -928,43 +1030,49 @@ public class EventQueue {
}
queues[i].tail = prev;
}
} finally {
pushPopLock.unlock();
}
}
static void setCurrentEventAndMostRecentTime(AWTEvent e) {
Toolkit.getEventQueue().setCurrentEventAndMostRecentTimeImpl(e);
}
private synchronized void setCurrentEventAndMostRecentTimeImpl(AWTEvent e)
{
if (Thread.currentThread() != dispatchThread) {
return;
}
private void setCurrentEventAndMostRecentTimeImpl(AWTEvent e) {
pushPopLock.lock();
try {
if (Thread.currentThread() != dispatchThread) {
return;
}
currentEvent = new WeakReference(e);
currentEvent = new WeakReference(e);
// This series of 'instanceof' checks should be replaced with a
// polymorphic type (for example, an interface which declares a
// getWhen() method). However, this would require us to make such
// a type public, or to place it in sun.awt. Both of these approaches
// have been frowned upon. So for now, we hack.
//
// In tiger, we will probably give timestamps to all events, so this
// will no longer be an issue.
long mostRecentEventTime2 = Long.MIN_VALUE;
if (e instanceof InputEvent) {
InputEvent ie = (InputEvent)e;
mostRecentEventTime2 = ie.getWhen();
} else if (e instanceof InputMethodEvent) {
InputMethodEvent ime = (InputMethodEvent)e;
mostRecentEventTime2 = ime.getWhen();
} else if (e instanceof ActionEvent) {
ActionEvent ae = (ActionEvent)e;
mostRecentEventTime2 = ae.getWhen();
} else if (e instanceof InvocationEvent) {
InvocationEvent ie = (InvocationEvent)e;
mostRecentEventTime2 = ie.getWhen();
// This series of 'instanceof' checks should be replaced with a
// polymorphic type (for example, an interface which declares a
// getWhen() method). However, this would require us to make such
// a type public, or to place it in sun.awt. Both of these approaches
// have been frowned upon. So for now, we hack.
//
// In tiger, we will probably give timestamps to all events, so this
// will no longer be an issue.
long mostRecentEventTime2 = Long.MIN_VALUE;
if (e instanceof InputEvent) {
InputEvent ie = (InputEvent)e;
mostRecentEventTime2 = ie.getWhen();
} else if (e instanceof InputMethodEvent) {
InputMethodEvent ime = (InputMethodEvent)e;
mostRecentEventTime2 = ime.getWhen();
} else if (e instanceof ActionEvent) {
ActionEvent ae = (ActionEvent)e;
mostRecentEventTime2 = ae.getWhen();
} else if (e instanceof InvocationEvent) {
InvocationEvent ie = (InvocationEvent)e;
mostRecentEventTime2 = ie.getWhen();
}
mostRecentEventTime = Math.max(mostRecentEventTime, mostRecentEventTime2);
} finally {
pushPopLock.unlock();
}
mostRecentEventTime = Math.max(mostRecentEventTime, mostRecentEventTime2);
}
/**
@ -1045,15 +1153,18 @@ public class EventQueue {
* or starts a new one otherwise.
*/
private void wakeup(boolean isShutdown) {
synchronized(this) {
pushPopLock.lock();
try {
if (nextQueue != null) {
// Forward call to the top of EventQueue stack.
nextQueue.wakeup(isShutdown);
} else if (dispatchThread != null) {
notifyAll();
pushPopCond.signalAll();
} else if (!isShutdown) {
initDispatchThread();
}
} finally {
pushPopLock.unlock();
}
}
}

View file

@ -248,14 +248,14 @@ public final class AWTAccessor {
* An accessor for the EventQueue class
*/
public interface EventQueueAccessor {
/*
* Gets the next event queue.
*/
EventQueue getNextQueue(EventQueue eventQueue);
/*
* Gets the event dispatch thread.
*/
Thread getDispatchThread(EventQueue eventQueue);
/*
* Checks if the current thread is EDT for the given EQ.
*/
public boolean isDispatchThreadImpl(EventQueue eventQueue);
}
/*

View file

@ -43,6 +43,9 @@ import java.util.HashSet;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
import sun.util.logging.PlatformLogger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* The AppContext is a table referenced by ThreadGroup which stores
@ -132,10 +135,17 @@ public final class AppContext {
/* Since the contents of an AppContext are unique to each Java
* session, this class should never be serialized. */
/* The key to put()/get() the Java EventQueue into/from the AppContext.
/*
* The key to put()/get() the Java EventQueue into/from the AppContext.
*/
public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue");
/*
* The keys to store EventQueue push/pop lock and condition.
*/
public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock");
public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition");
/* A map of AppContexts, referenced by ThreadGroup.
*/
private static final Map<ThreadGroup, AppContext> threadGroup2appContext =
@ -244,6 +254,13 @@ public final class AppContext {
return Thread.currentThread().getContextClassLoader();
}
});
// Initialize push/pop lock and its condition to be used by all the
// EventQueues within this AppContext
Lock eventQueuePushPopLock = new ReentrantLock();
put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock);
Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition();
put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond);
}
private static final ThreadLocal<AppContext> threadAppContext =

View file

@ -722,13 +722,7 @@ public abstract class SunToolkit extends Toolkit
EventQueue eq = (EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY);
AWTAccessor.EventQueueAccessor accessor = AWTAccessor.getEventQueueAccessor();
EventQueue next = accessor.getNextQueue(eq);
while (next != null) {
eq = next;
next = accessor.getNextQueue(eq);
}
return (Thread.currentThread() == accessor.getDispatchThread(eq));
return accessor.isDispatchThreadImpl(eq);
}
public Dimension getScreenSize() {

View file

@ -0,0 +1,103 @@
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/*
@test
@bug 4913324
@author Oleg Sukhodolsky: area=eventqueue
@run main/timeout=30 PushPopTest
*/
import java.awt.*;
import java.awt.event.*;
import java.util.EmptyStackException;
import sun.awt.SunToolkit;
public class PushPopTest {
public static Frame frame;
public static void main(String[] args) {
frame = new Frame("");
frame.pack();
Runnable dummy = new Runnable() {
public void run() {
System.err.println("Dummy is here.");
}
};
EventQueue seq = Toolkit.getDefaultToolkit().getSystemEventQueue();
MyEventQueue1 eq1 = new MyEventQueue1();
MyEventQueue2 eq2 = new MyEventQueue2();
EventQueue.invokeLater(dummy);
seq.push(eq1);
EventQueue.invokeLater(dummy);
eq1.push(eq2);
EventQueue.invokeLater(dummy);
Runnable runnable = new Runnable() {
public void run() {
System.err.println("Dummy from SunToolkit");
}
};
InvocationEvent ie = new InvocationEvent(eq2, runnable, null, false);
System.err.println(ie);
SunToolkit.postEvent(SunToolkit.targetToAppContext(frame), ie);
eq1.pop();
frame.dispose();
}
}
class MyEventQueue1 extends EventQueue {
public void pop() throws EmptyStackException {
super.pop();
}
}
class MyEventQueue2 extends EventQueue {
protected void pop() throws EmptyStackException {
System.err.println("pop2()");
Thread.dumpStack();
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
Runnable runnable = new Runnable() {
public void run() {
System.err.println("Dummy from here");
}
};
InvocationEvent ie = new InvocationEvent(MyEventQueue2.this, runnable, null, false);
SunToolkit.postEvent(SunToolkit.targetToAppContext(PushPopTest.frame), ie);
postEvent(ie);
}
});
} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (java.lang.reflect.InvocationTargetException ie) {
ie.printStackTrace();
}
super.pop();
}
}