United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6623943 javax.swing.TimerQueue's thread occasionally fails to start
JDK-6623943 : javax.swing.TimerQueue's thread occasionally fails to start

Details
Type:
Bug
Submit Date:
2007-10-31
Status:
Closed
Updated Date:
2011-03-08
Project Name:
JDK
Resolved Date:
2011-03-07
Component:
client-libs
OS:
generic
Sub-Component:
javax.swing
CPU:
generic
Priority:
P2
Resolution:
Fixed
Affected Versions:
7
Fixed Versions:

Related Reports
Backport:
Relates:

Sub Tasks

Description
The IRIS demo application developed for JavaOne 2007 (http://iris.dev.java.net/ , http://swinglabs.org/iris/) exhibits a problem where the application occasionally fails to start successfully; it will not load any users' albums nor perform many other actions.

The symptom is lost property change notifications deep in the SwingWorker code: changes to the "state" bound property were not propagating out, causing the Swing Application Framework to fail to notify the "succeeded" / "failed" listeners on its Tasks, causing Iris to fail to update the web page after loading a user's albums from Flickr.

The root cause is the use of a one-shot Swing timer in the DoSubmitAccumulativeRunnable inner class of SwingWorker. For some reason, occasionally the first javax.swing.Timer created and started in the system will fail to fire. This will cause all subsequent calls to SwingWorkerPropertyChangeSupport.firePropertyChange() to be enqueued on a list that will never be processed.

It seems that if the first Timer successfully fires, then all subsequent ones will.

Commenting out the definition of DoSubmitAccumulativeRunnable.submit(), using the superclass's version which calls SwingUtilities.invokeLater(), works around the problem.

Working with ###@###.### indicates that the problem appears to be that in some circumstances the per-AppContext TimerQueue thread fails to start.

The problem is related to the changes to the Swing Timer and TimerQueue from 5053997. Reverting these classes in a JDK 7 workspace to the pre-5053997 versions (as well as dependent classes) works around the problem.

                                    

Comments
EVALUATION

This bug is a regression introduced by the fix for 5053997 [Swing Timer cannot handle multiple timers effectively]

TimerQueue.start method starts the TimerQueue's thread and sets 'running' property to true. TimerQueue.run method is runing in the TimerQueue's thread until 'running' property is false.

Before the fix for 5053997 start and run methods were synchronized. So 'running' property were set to true before TimerQueue.run method was executed. After the fix for 5053997 there is no synchronization there and thus TimerQueue.run might be executed before 'running' is set to true and in this case it will exit immediately.
                                     
2007-11-12
EVALUATION

suggested fix:
===================
# HG changeset patch
# User idk
# Date 1214248897 14400
# Node ID fc09152d5cf67bb2fece572cdd074734fd9bd7ac
# Parent  fa7147a26cd25dbba698753ca524a680db6f6e43
6623943: javax.swing.TimerQueue's thread occasionally fails to start
Reviewed-by: alexp

diff --git a/src/share/classes/javax/swing/JApplet.java b/src/share/classes/javax/swing/JApplet.java
--- a/src/share/classes/javax/swing/JApplet.java
+++ b/src/share/classes/javax/swing/JApplet.java
@@ -131,10 +131,7 @@
         // Check the timerQ and restart if necessary.
         TimerQueue q = TimerQueue.sharedInstance();
         if(q != null) {
-            synchronized(q) {
-                if(!q.running)
-                    q.start();
-            }
+            q.startIfNeeded();
         }
 
         /* Workaround for bug 4155072.  The shared double buffer image
diff --git a/src/share/classes/javax/swing/TimerQueue.java b/src/share/classes/javax/swing/TimerQueue.java
--- a/src/share/classes/javax/swing/TimerQueue.java
+++ b/src/share/classes/javax/swing/TimerQueue.java
@@ -31,6 +31,7 @@
 
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
 import java.util.concurrent.atomic.AtomicLong;
 import sun.awt.AppContext;
 
@@ -52,7 +53,8 @@
         new StringBuffer("TimerQueue.expiredTimersKey");
 
     private final DelayQueue<DelayedTimer> queue;
-    volatile boolean running;
+    private volatile boolean running;
+    private final Lock runningLock;
 
     /* Lock object used in place of class object for synchronization.
      * (4187686)
@@ -69,7 +71,8 @@
         super();
         queue = new DelayQueue<DelayedTimer>();
         // Now start the TimerQueue thread.
-        start();
+        runningLock = new ReentrantLock();
+        startIfNeeded();
     }
 
 
@@ -87,32 +90,29 @@
     }
 
 
-    synchronized void start() {
-        if (running) {
-            throw new RuntimeException("Can't start a TimerQueue " +
-                                       "that is already running");
-        }
-        else {
-            final ThreadGroup threadGroup =
-                AppContext.getAppContext().getThreadGroup();
-            java.security.AccessController.doPrivileged(
-                new java.security.PrivilegedAction() {
-                public Object run() {
-                    Thread timerThread = new Thread(threadGroup, TimerQueue.this,
-                                                    "TimerQueue");
-                    timerThread.setDaemon(true);
-                    timerThread.setPriority(Thread.NORM_PRIORITY);
-                    timerThread.start();
-                    return null;
-                }
-            });
-            running = true;
+    void startIfNeeded() {
+        if (! running) {
+            runningLock.lock();
+            try {
+                final ThreadGroup threadGroup =
+                    AppContext.getAppContext().getThreadGroup();
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedAction() {
+                    public Object run() {
+                        Thread timerThread = new Thread(threadGroup, TimerQueue.this,
+                                                        "TimerQueue");
+                        timerThread.setDaemon(true);
+                        timerThread.setPriority(Thread.NORM_PRIORITY);
+                        timerThread.start();
+                        return null;
+                    }
+                });
+                running = true;
+            } finally {
+                runningLock.unlock();
+            }
         }
     }
-
-     synchronized void stop() {
-         running = false;
-     }
 
     void addTimer(Timer timer, long delayMillis) {
         timer.getLock().lock();
@@ -164,6 +164,7 @@
 
 
     public void run() {
+        runningLock.lock();
         try {
             while (running) {
                 try {
@@ -195,14 +196,14 @@
             }
         }
         catch (ThreadDeath td) {
-            synchronized (this) {
-                running = false;
-                // Mark all the timers we contain as not being queued.
-                for (DelayedTimer delayedTimer : queue) {
-                    delayedTimer.getTimer().cancelEvent();
-                }
-                throw td;
+            // Mark all the timers we contain as not being queued.
+            for (DelayedTimer delayedTimer : queue) {
+                delayedTimer.getTimer().cancelEvent();
             }
+            throw td;
+        } finally {
+            running = false;
+            runningLock.unlock();
         }
     }
 

===================
                                     
2008-06-23



Hardware and Software, Engineered to Work Together