JDK-6424157 : java.awt.EventQueue push/pop might cause threading issues
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 6,6u21
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-05-10
  • Updated: 2016-03-19
  • Resolved: 2010-07-21
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b102Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
J2SE Version (please include all output from java -version flag):
  java version "1.6.0-beta2"
  Java(TM) SE Runtime Environment (build 1.6.0-beta2-b81)
  Java HotSpot(TM) Client VM (build 1.6.0-beta2-b81, mixed mode, sharing)
 
  java version "1.5.0_07"
  Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b02)
  Java HotSpot(TM) Client VM (build 1.5.0_07-b02, mixed mode, sharing)
 
Does this problem occur on J2SE 1.4.x or 5.0.x ?
  Yes, the issue is a general problem, which concers Tiger and Mustang.
 
Operating System Configuration Information (be specific): 
   Windows XP SP2 Professional / DE

Hardware Configuration Information (be specific):
   Intel Pentium Mobile on VAIO VGN-FS 1.6 GHz
 
Bug Description:
   java.awt.EventQueue push/pop might cause threading issues

Steps to Reproduce (be specific):
   The issue rather concerns a general problem of the java.awt.EventQueue 
   class than a specific bug:
 
   The following typical scenario might occur in a Swing application:
 
   1. Assume the following EventQueue utility class:
 
    public class EventPump extends java.awt.EventQueue {
        
        private static final EventQueue EVENTQUEUE = java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue();
        
        public static EventPump push() {
            EventPump pump = new EventPump();
            EVENTQUEUE.push(pump);
            return pump;
        }
        
        protected void pop() {
            try {
                  super.pop();
            }
            catch (Exception ex) { 
                       ex.printStackTrace();
            }
        }
    }
 
 
  2. Assume that the application wants to execute some code on a separate 
     thread invoked from inside the awt thread.  In addition, the current 
     awt thread should pause for the separate thread to terminate, i.e. 
     something like ...
 
// here we assume being on the awt thread
Thread worker = new Thread() {
    public void run() {
        // do something ...
    }
}
worker.start();
worker.join();
// here the awt thread is waiting for the worker
 
 
  3. In order NOT  to freeze the gui, the code snippet 2. would be extended 
     using the utility 1. above:
 
 
Thread worker = new Thread() {
    public void run() {
        // do something ...
    }
}
worker.start();
EventPump keepOnGoing = EventPump.push();
worker.join();
 
keepOnGoing.pop().
 
 
  4. Now, looking at 3. and also having the source code of java.awt.EventQueue 
     and java.awt.EventDispatchThread in mind, we can make several observations.
     Note that the implementation is in principle the same for Tiger and Mustang
     concering the push, pop, initDispatchThread, stopDispatching, 
     stopDispatchingLater etc. methods relevant for the subsequent threading 
     dicussion. Hence, the following notes apply to both Java versions.
 
  a. When executing push(), the following happens:
  a.i. A new EventQueue Q2 is pushed on top of the EventQueue stack.
  a.ii. All pending events are transferred from the current EventQueue Q1 to Q2.
  a.iii. The thread T1 of the original EventQueue is stopped lazily, which is 
    implemented by setting the boolean doDispatch flag, which finally terminates
    the thread's run() method when the thread the next time is trying to pump 
    an event.
  a.iv. T1 is detached from Q1 by executing the theQueue.detachThread() method.
  a.v. For instance, T1 is still running because it is currently dispatching 
       scenario 3. above and will execute the join() method first.
  a.vi. Once, a new event is arriving on the currently active EventQueue Q2, 
        a new awt thread T2 is created for Q2, which starts pumping events on Q2.
 
  b. Once, the worker thread of scenario 3. above has terminated, the pop() 
     method is executed by T1, which has the following effect:
  b.i. All pending events are transferred from Q2 to Q1.
  b.ii. The current dispatch Thread T2 is stopped by T1, i.e. b putting a 
        so-called StopEvent into Q2, which indirectly modifies the above-cited 
        doDispatch flag, when the StopEvent is dispatched by T2.  Note, this 
        time T1 is waiting for this to happen.
 
  Now, when Q1 resumes pumping events, it has no current dispatch thread due to
  item a.iv above.  Consequently, a new thread T1* is created for Q1, which gets
  the new active awt thread.
 
  Note, the javax.swing.SwingUtilities.isEventDispatchThread() method will 
  correctly supply "true"  if and only if now called from T1*.  Hence, the 
  method would correctly evaluate to "false" when called by T1 immeadiately 
  after the pop() method call of the avove-cited scenario.
 
Problem:
  Immediately, before scenario 3. terminates, we have two active threads (with 
  the same name), i.e. T1*, the new awt thread, and T1 the original awt thread.
  T1 is still dispatching its event, which might hence interfere with newly 
  arriving events dispatched by T1*.
  Clearly, speaking there is room for potential threading issues for the period
  between the start of T1*  (after calling pop() above) and the dead of T1.
 
  Imagine that T1 modifies variables before dying. Such changes would only be 
  visible to T1* when properly synchronized.  By constrast, developers relying 
  on the "only one dispatch thread at the time" paradigm would probably not see
  a reason for synchronization in this context.
 
 
Proposed Solution:
  Instead of detaching T1 from its queue in a.iv above and stopping it, it might
  be better (also for performance and resource aspects) , NOT to detach an awt 
  thread when pushing a new EventQueue. The awt thread should just be lazily 
  stopped and hence have a chance to resume dispatching on its original queue, 
  when pop() is called before lazy stopping really happens.
 
  As a consequnce, the original queue would keep its original awt thread when 
  resuming work in the above-cited scenario, which seems to be a typical 
  EventQueue push/pop use case. The original awt thread could continue pumping 
  and there would be no need for synchronization. In addition, the 
  SwingUtilities.isEventDispatchThread() would continue to supply "true" for T1
  in the above-cited scenario after pop(), which might be a more comprehensive 
  behavior from the user's point of view.

Comments
EVALUATION Re-using a single event dispatch thread for different event queues is a compatible change, because there is no exact specification on how event queues are stacked. However, many people use push/pop methods exactly to fork a new dispatch thread, and their applications would be broken. The only known reason of the forking is when an application should stop EDT until a certain operation is done, but at the same time needs to be repainted correctly. This is achieved by pushing a new event queue (and thus making the current dispatch thread be non-dispatch thread any more) and showing a modal dialog. When the operation is completed, the dialog is hidden and the thread is resumed - but it's not EDT anymore. To resolve this conflict, we need to provide an API to run the nested message loop - this is what 6949936 about. It should be implemented right after this fix is integrated.
01-07-2010

EVALUATION The approach described in the description is reasonable, however it would further complicate AWT event dispatching. Instead, I would suggest quite the opposite approach: share a single event dispatch thread between all the pushed/popped event queues. That is, we shouldn't call EDT.stopDispatching() or stopDispatchinLater() when an event queue is pushed or popped, but just switch the EDT's event queue.
01-07-2010

EVALUATION the suggestion looks reasonable. Should try to evaluate and implement it in dolphin
10-05-2006