Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4144467

AWT deadlock; JComponent.revalidate or SwingUtilities.isEventDispatchThread bug

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P3 P3
    • None
    • 1.2.0
    • client-libs



      Name: rm29839 Date: 06/01/98


      In Swing 1.0.2, there appears to be a bug in either
      SwingUtilities.isEventDispatchThread() or
      JComponent.revalidate().

      The bug can lead to deadlock (frozen, unresponsive,
      gray windows) when it interacts with read and
      write locking in AbstractDocument.

      First look at JComponent.revalidate(). Here is
      the code from JComponent.java, near line 2903:

          public void revalidate() {
              if (SwingUtilities.isEventDispatchThread()) {
                  invalidate();
                  RepaintManager.currentManager(this).addInvalidComponent(this);
              }
              else {
                  Runnable callRevalidate = new Runnable() {
                      public void run() {
                          revalidate();
                      }
                  };
                  SwingUtilities.invokeLater(callRevalidate);
              }
          }

      Apparently, revalidate is trying to determine
      whether it is running on the currently active AWT
      event-handling thread.

      (If it is, it will initiate window invalidation
      directly. Otherwise (if it is not running on the
      event-handling thread), it takes special measure
      to run window invalidation on the event-handling
      thread.)

      To find out if it is running on the event-handling
      thread or not, revalidate calls
      SwingUtilities.isEventDispatchThread.

      However, that call does not determine whether the
      current thread is the _active_ dispatch thread;
      it only checks whether the thread is _a_ dispatch
      thread. (Explained below.)

      If a modal dialog was shown from the original
      dispatch thread, there will be a proxy dispatch
      thread that takes over while the original dispatch
      thread is suspended.

      (The proxy dispatch thread is the one created in
      Dialog.show():
              dt = new EventDispatchThread("AWT-Dispatch-Proxy",
                                           Toolkit.getEventQueue());

      )


      The call be revalidate to
      SwingUtilities.isEventDispatchThread checks only
      whether the current thread is _a_ dispatch thread.
      It does not check whether any other dispatch
      thread exists.

      Specifically, it checks only the class of the
      thread object; it doesn't check any indication
      of which dispatch thread is currently active, or
      how many are active.

      Here's the code:


          /**
           * @return true if the current thread is an AWT event dispatching thread.
           */
          public static boolean isEventDispatchThread()
          {
              Thread currentThread = Thread.currentThread();

              /* The first time we're called on what appears to be the event
               * dispatching thread, we stash the threads class in
               * eventDispatchThreadClass. Subsequently we effectively
               * return eventDispatchThreadClass instanceof Thread.currentThread().
               */

              if (eventDispatchThreadClass == null) {
                  Class currentThreadClass = currentThread.getClass();

                  /* This test is a crock. It's known to work on all of the popular
                   * JDK1.1 implementations available as of January 1998.
                   */
                  if((currentThreadClass.getName().indexOf("EventDispatchThread") >= 0) ||
                     (currentThreadClass.getName().indexOf("JMEventQueue") >= 0)) {
                      eventDispatchThreadClass = currentThreadClass;
                      return true;
                  }
                  else {
                      return false;
                  }
              }
              return eventDispatchThreadClass.isInstance(currentThread);
          }

      (Note: Below here I'm highlighting "_re_validate"
      vs. "_in_validate" to try to avoid confusion
      between the two.)


      If JComponent._re_validate is called from the
      original dispatch thread while a proxy dispatch
      thread is active, _re_validate won't cause
      _in_validate to be invoked on the proxy dispatch
      thread; it will begin to execute it directly on
      its own thread, which is the original dispatch
      thread.

      If the proxy dispatch thread is active (painting
      an apparently taking locks), this can lead to
      deadlock.


      I think that isEventDispatchThread (or revalidate)
      should check whether the current thread is the
      _active_ dispatch thread, not just _a_ dispatch
      thread.

      Maybe you need to more formally define whether
      there is one active thread, and then define whether
      isEventDispatchThread is supposed to indicate
      whether you're on _a_ dispatch thread or _the_
      active one.







      Here's the deadlock situation I encountered and
      what I think happened. It involved a modal dialog
      and the read and write locking of AbstractDocument.



      I ran my application, which created a top-level
      window with some buttons.

      I clicked on a button in that top-level window.
      The button listener code created a dialog, set
      it to model, and showed it. This blocked the
      original dispatch thread, right after it started
      up a proxy dispatch thread.

      I clicked on a button in the modal dialog to
      cause it to dispose itself. This action took
      place on the proxy dispatch thread. It also
      unblocked the original dispatch thread.

      The original dispatch thread called stopDispatching
      so that the proxy dispatch thread would stop the
      next time it checked its doDispatch flag, and
      then continued with the original button listener
      code.

      Apparently, before doDispatch was cleared by
      the original dispatch thread, the proxy dispatch
      thread had already gotten started servicing some
      repaint event (as evidenced by a multi-thread
      stack trace (the thing labeled "Full stack dump")
      taken after things locked up).


      Back on the newly-resumed original dispatch thread,
      my button listener code called the insertString
      method of (my subclass of) an AbstractDocument.
      That method called writeLock, modified the data
      structures, and then began notifying listeners,
      including the renderers (e.g., BoxView,
      ParagraphView).

      Meanwhile, the proxy dispatch thread had started
      to repaint the top-level window. (This seemed to
      happen whether or not the modal dialog box was
      obscuring any part of the top-level window at
      the time it was dismissed.)

      Deep in a chain of calls to paint and paintChildren,
      the proxy thread called AbstractDocument.render,
      which called AbstractDocument.readLock, which
      blocked because the original dispatch thread had
      the write lock. Now the proxy dispatch thread
      was blocked, waiting on the AbstractDocument lock.
      Apparently, it was also holding some lock(s)
      related to UI components.


      Back in the original dispatch thread, some
      renderer called JComponent.revalidate (from deep
      under the document-modification code that grabbed
      the write lock). Revalidate apparently tried
      invoking _in_validate directly (on the same
      thread (the original dispatch thread)).

      Apparently, _in_validate blocked on the orignal
      dispatch thread because the proxy dispatch
      thread was in the middle of painting, and had
      lock something (UI components?). Of course,
      the proxy dispatch thread had blocked because it
      couldn't get a read lock for the document data
      to repaint.

      The window was all gray and the application was
      unresponsive. The full stack trace that I got
      was taken from this state.



      If _re_validate had invoked _in_validate on the
      currently active dispatch thread (the proxy
      thread), it would not have blocked the original
      dispatch thread, so it could have finished up
      nested calls and gotten back to the document-
      modification method that had grabbed the write
      lock. That method would have called writeUnlock
      and released the lock.

      The proxy dispatch thread would no longer be
      blocked trying to read the document, so it would
      resume to read the data and paint the screen.
      (I assume it would then process the specially-
      invoked call to _in_validate.)



      I can't send a test case for this, because the
      problem is intermittent. On my main machine, it
      appears only occasionally. On another machine,
      it usually happens (but not always). It would
      take much to long to trim down my application
      given that the problem shows up only intermittently.

      The problem occurs on your JVM 1.1.6 (and 1.1.5),
      and also occurs (also intermittently) using
      whatever JVM is in Symantec's Cafe.


      However, here is the stack dump that I got.
      (I don't recall how it got chopped after
      "Registered Mo".)



      Full thread dump:
          "AWT-Dispatch-Proxy" (TID:0xf73340, sys_thread_t:0x8c2d00, Win32ID:0x218, state:CW) prio=5
      java.lang.Object.wait(Object.java:315)
      com.sun.java.swing.text.AbstractDocument.readLock(AbstractDocument.java:683)
      com.sun.java.swing.text.AbstractDocument.render(AbstractDocument.java:250)
      com.sun.java.swing.plaf.basic.BasicTextUI.paint(BasicTextUI.java:504)
      com.sun.java.swing.plaf.ComponentUI.update(ComponentUI.java:47)
      com.sun.java.swing.JComponent.paintComponent(JComponent.java:322)
      com.sun.java.swing.JComponent.paint(JComponent.java:534)
      com.sun.java.swing.JComponent.paintChildren(JComponent.java:382)
      com.sun.java.swing.JComponent.paint(JComponent.java:537)
      com.sun.java.swing.JComponent.paintChildren(JComponent.java:382)
      com.sun.java.swing.JComponent.paint(JComponent.java:537)
      com.sun.java.swing.JComponent.paintChildren(JComponent.java:382)
      com.sun.java.swing.JComponent.paint(JComponent.java:537)
      com.sun.java.swing.JComponent.paintChildren(JComponent.java:382)
      com.sun.java.swing.JComponent.paint(JComponent.java:537)
      com.sun.java.swing.JLayeredPane.paint(JLayeredPane.java:555)
      com.sun.java.swing.JComponent.paintChildren(JComponent.java:382)
      com.sun.java.swing.JComponent.paint(JComponent.java:522)
      java.awt.Container.paint(Container.java:702)
      java.awt.Component.dispatchEventImpl(Component.java:1723)
          "TimerQueue" (TID:0xf7f1b8, sys_thread_t:0x8c7b20, Win32ID:0x1f3, state:CW) prio=5
      com.sun.java.swing.TimerQueue.run(TimerQueue.java:210)
      java.lang.Thread.run(Thread.java:474)
          "Screen Updater" (TID:0xf86658, sys_thread_t:0x8c11c0, Win32ID:0x1d6, state:CW) prio=4
      java.lang.Object.wait(Object.java:315)
      sun.awt.ScreenUpdater.nextEntry(ScreenUpdater.java:78)
      sun.awt.ScreenUpdater.run(ScreenUpdater.java:98)
          "AWT-Windows" (TID:0xf654c0, sys_thread_t:0x890b00, Win32ID:0x1d8, state:R) prio=5
      sun.awt.windows.WToolkit.run(WToolkit.java:106)
      java.lang.Thread.run(Thread.java:474)
          "AWT-EventQueue-0" (TID:0xf652e0, sys_thread_t:0x8897d0, Win32ID:0x217, state:MW) prio=5
      java.awt.Component.invalidate(Component.java:1126)
      java.awt.Container.invalidate(Container.java:485)
      com.sun.java.swing.JComponent.revalidate(JComponent.java:2904)
      com.sun.java.swing.plaf.basic.BasicTextUI$RootView.preferenceChanged(BasicTextUI.java:788)
      com.sun.java.swing.text.View.preferenceChanged(View.java:84)
      com.sun.java.swing.text.BoxView.preferenceChanged(BoxView.java:119)
      com.sun.java.swing.text.View.preferenceChanged(View.java:84)
      com.sun.java.swing.text.BoxView.preferenceChanged(BoxView.java:119)
      com.sun.java.swing.text.ParagraphView.insertUpdate(ParagraphView.java:642)
      com.sun.java.swing.text.BoxView.insertUpdate(BoxView.java:327)
      com.sun.java.swing.plaf.basic.BasicTextUI$RootView.insertUpdate(BasicTextUI.java:908)
      com.sun.java.swing.plaf.basic.BasicTextUI$UpdateHandler.insertUpdate(BasicTextUI.java:1124)
      com.sun.java.swing.text.AbstractDocument.fireInsertUpdate(AbstractDocument.java:139)
      gendoc.core.data.MyTemplate.insertFieldInsertionPoint(MyTemplate.java:400)
      gendoc.core.render.MyTemplateEditorKit$InsertFieldInsertionPointAction.actionPerformed(MyTemplateEditorKit.java:258)
      gendoc.core.render.TemplateEditorPane.insertFieldInsertionPoint(TemplateEditorPane.java:144)
      gendoc.driver.TemplateEditorWindow.doInsertField(TemplateEditorWindow.java:703)
      gendoc.driver.TemplateEditorWindow.access$4(TemplateEditorWindow.java:665)
      gendoc.driver.TemplateEditorWindow$8.actionPerformed(TemplateEditorWindow.java:350)
      com.sun.java.swing.AbstractButton.fireActionPerformed(AbstractButton.java:861)
          "Finalizer thread" (TID:0xf60088, sys_thread_t:0x884380, Win32ID:0x208, state:CW) prio=2
          "main" (TID:0xf600b0, sys_thread_t:0x880b90, Win32ID:0x201, state:CW) prio=5
      Monitor Cache Dump:
          java.lang.Object@F63D68/FACC18: owner "AWT-Dispatch-Proxy" (0x8c2d00, 5 entries)
          gendoc.core.data.MyTemplate@F6EE70/FF31B0: <unowned>
              Waiters: 1
          com.sun.java.swing.TimerQueue@F7F198/FF56E0: <unowned>
              Waiters: 1
          sun.awt.ScreenUpdater@F86658/FB7F10: <unowned>
              Waiters: 1
      Registered Mo
      (Review ID: 32250)
      ======================================================================

            sswingtrsunw Swingtraq Swingtraq (Inactive)
            rmandelsunw Ronan Mandel (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: