United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6799345 JFC demos threw exception in the Java Console when applets are closed
JDK-6799345 : JFC demos threw exception in the Java Console when applets are closed

Details
Type:
Bug
Submit Date:
2009-01-30
Status:
Closed
Updated Date:
2011-10-18
Project Name:
JDK
Resolved Date:
2009-12-28
Component:
client-libs
OS:
generic
Sub-Component:
javax.swing
CPU:
generic
Priority:
P2
Resolution:
Fixed
Affected Versions:
7
Fixed Versions:

Related Reports
Backport:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:

Sub Tasks

Description
There is some exception threw in Java Console during JDK7-b45 SWAT testing.

Bundles location: http://jre.sfbay/java/re/jdk/7/nightly/bundles/<PLATFORM-ARCH>/latest 
OS tested: Windows Vista, Solaris Sparc 10, RHEL5.2

How to reproduce:
1. Install jre from the location above.
2. Use the browser to load SwingSet2 Applet or Java 2D from http://java.sun.com/products/plugin/1.5.0/demos/applets.html
3. The exception is printed to the Java Console when the applet is closed.
This is the stack trace of the exception:
Exception in thread "TimerQueue" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
	at java.util.concurrent.DelayQueue.take(Unknown Source)
	at javax.swing.TimerQueue.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
Exception in thread "SwingWorker-pool-1-thread-1" java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThreadGroup)
	at java.security.AccessControlContext.checkPermission(Unknown Source)
	at java.security.AccessController.checkPermission(Unknown Source)
	at java.lang.SecurityManager.checkPermission(Unknown Source)
	at sun.applet.AppletSecurity.checkAccess(Unknown Source)
	at java.lang.ThreadGroup.checkAccess(Unknown Source)
	at java.lang.Thread.init(Unknown Source)
	at java.lang.Thread.<init>(Unknown Source)
	at java.util.concurrent.Executors$DefaultThreadFactory.newThread(Unknown Source)
	at javax.swing.SwingWorker$6.newThread(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.<init>(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.addWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.processWorkerExit(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

It's a regression starting from b15.

                                    

Comments
EVALUATION

see comments
                                     
2009-02-05
SUGGESTED FIX

--- old/src/share/classes/javax/swing/SwingWorker.java	2009-02-09 18:59:53.000000000 +0300
+++ new/src/share/classes/javax/swing/SwingWorker.java	2009-02-09 18:59:52.000000000 +0300
@@ -778,35 +778,34 @@
                                        threadFactory);
             appContext.put(SwingWorker.class, executorService);
 
-            //register shutdown hook for this executor service
+            // Don't use ShutdownHook here as it's not enough. We should track
+            // AppContext disposal instead of JVM shutdown, see 6799345 for details
             final ExecutorService es = executorService;
-            final Runnable shutdownHook =
-                new Runnable() {
-                    final WeakReference<ExecutorService> executorServiceRef =
-                        new WeakReference<ExecutorService>(es);
-                    public void run() {
-                        final ExecutorService executorService =
-                            executorServiceRef.get();
-                        if (executorService != null) {
-                            AccessController.doPrivileged(
-                                new PrivilegedAction<Void>() {
-                                    public Void run() {
-                                        executorService.shutdown();
-                                        return null;
+            appContext.addPropertyChangeListener(AppContext.DISPOSED_PROPERTY_NAME,
+                new PropertyChangeListener() {
+                    @Override
+                    public void propertyChange(PropertyChangeEvent pce) {
+                        boolean wasDisposed = (Boolean)pce.getOldValue();
+                        boolean disposed = (Boolean)pce.getNewValue();
+                        if (!wasDisposed && disposed) {
+                            final WeakReference<ExecutorService> executorServiceRef =
+                                new WeakReference<ExecutorService>(es);
+                            final ExecutorService executorService =
+                                executorServiceRef.get();
+                            if (executorService != null) {
+                                AccessController.doPrivileged(
+                                    new PrivilegedAction<Void>() {
+                                        public Void run() {
+                                            executorService.shutdown();
+                                            return null;
+                                        }
                                     }
-                                });
+                                );
+                            }
                         }
                     }
-                };
-
-            AccessController.doPrivileged(
-                new PrivilegedAction<Void>() {
-                    public Void run() {
-                        Runtime.getRuntime().addShutdownHook(
-                            new Thread(shutdownHook));
-                        return null;
-                    }
-            });
+                }
+            );
         }
         return executorService;
     }
--- old/src/share/classes/javax/swing/TimerQueue.java	2009-02-09 18:59:54.000000000 +0300
+++ new/src/share/classes/javax/swing/TimerQueue.java	2009-02-09 18:59:54.000000000 +0300
@@ -191,7 +191,10 @@
                     } finally {
                         timer.getLock().unlock();
                     }
-                } catch (InterruptedException ignore) {
+                } catch (InterruptedException ie) {
+                    // Shouldn't ignore InterruptedExceptions here, so AppContext
+                    // is disposed gracefully, see 6799345 for details
+                    break;
                 }
             }
         }
                                     
2009-02-09
EVALUATION

There are 2 exceptions reported in the description, and it seems both are caused by different problems in Swing code.

First, javax.swing.TimerQueue.run() method silently ignores all the interruptions. I don't know why it is implemented this way, but it seems wrong to me, and every InterruptedException should result in a break statement.

Second exception occurs in javax.swing.SwingWorker code. This class uses ThreadPoolExecutor to spawn new worker threads, and even registers a ShutdownHook to shut it down correctly. Unfortunately, it is not enough as there may be several AppContexts in the application, and disposing one of them not necessarily leads to JVM shutdown. That's why the code from ShutdownHook should be refactored, for example, to listen to AppContext disposal.

See suggested fix for details.
                                     
2009-02-09



Hardware and Software, Engineered to Work Together