JDK-4291993 : deadlock in javax.swing.UIDefaults.get()
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.3.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 1999-11-16
  • Updated: 2000-12-14
  • Resolved: 2000-12-14
Related Reports
Duplicate :  
Relates :  
Description
The method javax.swing.UIDefaults.get(Object) may cause deadlock 
in multi-thread application. 

There are two 'synchronized' blocks in the method's code (displayed
below). I particular, those blocks of code process the case if 'key'
is assigned to 'LazyValue' which currently has 'PENDING' status: 
- the 1st block: waits while the 'PENDING' value be prepared 
  by some other Java-thread just exploring it, 
- and the 2nd block: prepares 'PENDING' value and notifies other 
  threads when the value is ready. 

However, deadlock is possible between these two blocks of code: 

Suppose, that some thread (let's denote it thread 'A') executes the 
line (marked below): 
    super.set(key,PENDING) 
and quits from the 1st synchronized block. 

At that moment, other thread (the thread 'B') may bring into the 1st 
syncronized block for the same UIDefaults instance and the same key. 
Since the status of the corresponding value is 'PENDING', that thread 
will never leave the synchronized block. 

The thread 'B' will prevent the thread 'A' from getting into the 2nd 
syncronized block, so that the thread 'A' will never have a chance to 
process that LazyValue and remove its 'PENDING' status. 

At that time, many other threads may hang awaiting for permission 
to get into the 1st synchronized block. 

    /**
     * Returns the value for key.  If the value is a
     * <code>UIDefaults.LazyValue</code> then the real
     * value is computed with <code>LazyValue.createValue()</code>,
     * the table entry is replaced, and the real value is returned.
     * If the value is an <code>UIDefaults.ActiveValue</code>
     * the table entry is not replaced - the value is computed
     * with ActiveValue.createValue() for each get() call.
     *
     * @see LazyValue
     * @see ActiveValue
     * @see java.util.Hashtable#get
     */
    public Object get(Object key)
    {
        /* Quickly handle the common case, without grabbing
         * a lock.
         */
        Object value = super.get(key);
        if ((value != PENDING) &&
            !(value instanceof ActiveValue) &&
            !(value instanceof LazyValue)) {
            return value;
        }

        /* If the LazyValue for key is being constructed by another
         * thread then wait and then return the new value, otherwise drop
         * the lock and construct the ActiveValue or the LazyValue.
         * We use the special value PENDING to mark LazyValues that
         * are being constructed.
         */
+---->  synchronized(this) { 
|           value = super.get(key);                                 
|           if (value == PENDING) {                                 
|               do {                                                
|                   try {                                           
|                       this.wait();                                
|                   }
|                   catch (InterruptedException e) {
|                   }
|                   value = super.get(key);
|               }
|               while(value == PENDING);
|               return value;
|           }
|           else if (value instanceof LazyValue) {
+----           super.put(key, PENDING);
|           }
|           else if (!(value instanceof ActiveValue)) {
|               return value;
|           }
|       }
|
|       /* At this point we know that the value of key was
|        * a LazyValue or an ActiveValue.
|        */
|       if (value instanceof LazyValue) {
|           try {
|               /* If an exception is thrown we'll just put the LazyValue
|                * back in the table.
|                */
|               value = ((LazyValue)value).createValue(this);
|           }
|           finally {
+------->       synchronized(this) {
                    if (value == null) {
                        super.remove(key);
                    }
                    else {
                        super.put(key, value);
                    }
                    this.notify();
                }
            }
        }
        else {
            value = ((ActiveValue)value).createValue(this);
        }

        return value;
    }

Indeed, probability of scenario situation seems low; but it increases 
dramatically if there are many threads exploring UIDefaults. 
In particular, the JCK-stress test 'jck12a017' oftenly hangs due 
to this scenario. (This test tries to execute 405 of JCK 1.2a 
API/swing tests simultaneously in concurent threads.) 

The stress test 'jck12a017' belongs to the 'testbase_nsk' testbase, and its 
sources could be found in the directory: 
    /net/sqesvr/vsn/testbase/testbase_nsk/src/nsk/stress/jck12a/jck12a017

To execute this test, please follow these instructions: 

    1. Create some temporary directory, to say C:\TEMP\jck12a017, 
       and make this directory your current directory:
           cd C:\TEMP\jck12a017

    2. Copy the test's sources into this directory. You need to copy 
       only the file 'jck12a017.java', and the files 'jck12a017.cfg' 
       and 'jck12a017.README' are optional.

    3. Also copy into the current directory the stress-wrapper needed to 
       execute the test. The wrapper is the 'StressTest.java' file found 
       in the directory:
           /net/sqesvr/vsn/testbase/testbase_nsk/src/stress/share

    4. Create 'classes' subdirectory for the wrapper's class-files you will 
       need to compile:
           mkdir classes

    5. Mount to the drive L: the NFS directory:
        /usr/local/java
       (it is available as \\grinder\local-java for NT machines)

    6. Setup the CLASSPATH to provide the test with JavaTest 2.0 tool 
       and JCK 1.2a pre-compiled API tests:
           set CLASSPATH=.;.\classes;L:\sqe-tools2.0\javatest.jar;L:\jck1.2\JCK-runtime-api-12a\classes

    7. Compile the wrapper with JDK 1.2, JDK 1.2.2 or JDK 1.3 compiler:
           javac -d .\classes StressTest.java

    8. Compile the test with JDK 1.2, JDK 1.2.2 or JDK 1.3 compiler:
           javac jck12a017.java

    9. Execute the test under debugger:
           jdb -J-classic jck12a017
           >run
       (Please, turn off HotSpot, because it crashes under this test: see bug #4288475) 

    10. When the test hangs after 3-5 minutes, see list of deadlocked threads 
        and where they are hanged, e.g.:

           >threads
Group main:
    (java.lang.Thread)0x1
    (javasoft.sqe.stresstest.StressTest$TestThread)0x384
    (javasoft.sqe.stresstest.StressTest$TestThread)0x389
    (javasoft.sqe.stresstest.StressTest$TestThread)0x45e
    (javasoft.sqe.stresstest.StressTest$TestThread)0x491
    (javasoft.sqe.stresstest.StressTest$TestThread)0x492
    (javasoft.sqe.stresstest.StressTest$TestThread)0x499
    (javasoft.sqe.stresstest.StressTest$TestThread)0x49c
    (javasoft.sqe.stresstest.StressTest$TestThread)0x4da
    (javasoft.sqe.stresstest.StressTest$TestThread)0x4dc
    (javasoft.sqe.stresstest.StressTest$TestThread)0x4f0
    (javasoft.sqe.stresstest.StressTest$TestThread)0x4f1
    (javasoft.sqe.stresstest.StressTest$TestThread)0x503
    (javasoft.sqe.stresstest.StressTest$TestThread)0x505
    (java.awt.EventDispatchThread)0x6b5
    (sun.awt.PostEventQueue)0x6b8
    (java.lang.Thread)0x6bd
    (java.lang.Thread)0x75d
    (javasoft.sqe.tests.api.javax.swing.text.AbstractDocument.MyThread)0xa2d
    (sun.awt.ScreenUpdater)0x978

           >suspend
           >where 0x384
0x384: 
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Object.wait (Object.java:424)
  [3] javax.swing.UIDefaults.get (UIDefaults.java:119)  -- this.wait() -- 1st syncronized block --
  [4] javax.swing.MultiUIDefaults.get (MultiUIDefaults.java:50)
  [5] javax.swing.UIDefaults.getBorder (UIDefaults.java:245)
  [6] javax.swing.UIManager.getBorder (UIManager.java:490)
  [7] javax.swing.LookAndFeel.installBorder (LookAndFeel.java:111)
  [8] javax.swing.plaf.basic.BasicButtonUI.installDefaults (BasicButtonUI.java:132)
  [9] javax.swing.plaf.metal.MetalButtonUI.installDefaults (MetalButtonUI.java:58)
  [10] javax.swing.plaf.basic.BasicButtonUI.installUI (BasicButtonUI.java:67)
  [11] javax.swing.JComponent.setUI (JComponent.java:326)
  [12] javax.swing.AbstractButton.setUI (AbstractButton.java:1147)
  [13] javax.swing.JButton.updateUI (JButton.java:122)
  [14] javax.swing.AbstractButton.init (AbstractButton.java:1423)
  [15] javax.swing.JButton.<init> (JButton.java:112)
  [16] javax.swing.JButton.<init> (JButton.java:87)
  [17] javasoft.sqe.tests.api.javax.swing.border.BevelBorder.getXXXTests.BevelBorder2006 (getXXXTests.java:91)
  [18] java.lang.reflect.Method.invoke (native method)
  [19] javasoft.sqe.jck.lib.MultiTest.run (MultiTest.java:137)
  [20] javasoft.sqe.stresstest.StressTest$TestThread.run (StressTest.java:723)

Comments
EVALUATION I disagree with the assesment (of course I admit I could be wrong). I believe that thread 'B' will release the lock when it does the wait, at this point thread 'A' can then enter the synchronized block and reset the value. It will then wake thread B via the notify and then B should then able to continue. The problem here is if you have multiple threads waiting, notifyAll should be used instead of notify. scott.violet@eng 1999-11-17 As I said, the problem with this is no notifyAll, which is why some threads can get wedged. This was fixed as part of 4327067, refer to it for more details. scott.violet@eng 2000-12-14
14-12-2000

SUGGESTED FIX Name: pzR10082 Date: 06/16/2000 Changing notify() to notifyAll() really helps: ------- UIDefaults.java ------- *** /tmp/dVyaiBd Fri Jun 16 14:33:12 2000 --- UIDefaults.java Fri Jun 16 14:33:14 2000 *************** *** 152,158 **** else { super.put(key, value); } ! this.notify(); } } } --- 152,158 ---- else { super.put(key, value); } ! this.notifyAll(); } } } ###@###.### 2000-06-16 ======================================================================
16-06-2000