JDK-4290274 : (timer) java.util.Timer.scheduleAtFixedRate() fails if the system time is changed
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util
  • Affected Version: 6
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: generic,windows_nt,windows_xp
  • CPU: generic,x86
  • Submitted: 1999-11-11
  • Updated: 2018-08-21
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
==per the spec of scheduleAtFixedRate():

"It is also appropriate for for recurring activities where the total 
time to perform a fixed number of executions is important, such as 
a countdown timer that ticks once every second for ten seconds."

This breaks if the system time is changed.

For example, 

- a timer task is scheduled to repeat every 30 seconds 

        final long PERIOD = 30 * 1000;    // milliseconds
        Timer tm =  new Timer();
        Date current_date = new Date() ;
        tm.scheduleAtFixedRate(task1, 
			       current_date,
			       PERIOD) ;
        
- then the system clock is changed (let's say, it's set back 1 minute)

Expected: task1 is fired every 30 seconds

Actual  : task1 is not fired for 1 minute.
	  (while the system clock is < current_date).
	  Two executions are missed.

Similarly, two extra executions are fired (unexpectedly) when the system clock
is set ahead one minute after the task is scheduled using scheduleAtFixedRate().




Name: boT120536			Date: 07/31/2001


C:\JBuilder4\jdk1.3\bin>java -version
java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)

After a swing timer is started and before that timer is expired, a software
tester changes the system time of the local host, say, delay the current system
time by one day.  The timer also delays its expiration time for a day.

We dont expect the timer be delayed eventhough the current system time is
delayed by a significant amount of time.  We also do not expect the timer
behave different if the current system time is set forward.

I have looked at the swing Timer.java and TimerQueue.java classes, and the
TimerQueue.java has a run() method and that calls postExpiredTimers() method.
The postExpiredTimers() method calls the System.currentTimeMillis() to update
the timeToWaitvariable, that is the problem.  Because TimerQueue has default
access modifier, I cannot extend it and hence I cannot override the above
two methods.  I would appreciate if you could fix this problem so that
the Timer always fires ActionEvent as long as the predefined timeout comes
to an end.  Don't let the system time change to affect the timer's behavior.

Regards,
Lee Zhou
Waterford Institute
###@###.###
(Review ID: 126004)
======================================================================

Name: ddT132432			Date: 11/30/2001


D:\>java -version
java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)

This behavior also happens in JDK1.4beta3.

/**
 * class TimerTest
 *
 * Class used to demonstrate failure of the javax.swing.Timer after the system
 * clock has been set back.
 *
 * Steps to demonstrate bug:
 *
 * 1. Start this program running by calling its main( )
 * 2. A Frame will come up that shows the current time in a JLabel.
 *    Note that the text of the JLabel is updated every second by
 *    a swing timer.
 * 3. While the program is up and running, set the system clock back, for
 *    example set the computer's clock back by one hour. Now notice that
 *    the time is no longer being updated. That is, the op sys clock continues
 *    to run and update itself, but the time read out in the java JLabel
 *    is no longer updating.
 *
 * NOTE: I have also tried 1.4beta3 and still the same results.
 *
 * @author hill
 * Date: Nov 15, 2001
 * Time: 1:52:06 PM
 */
package timertest;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.SwingConstants;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.Calendar;

public class TimerTest extends JLabel
{
    private       SimpleDateFormat dateFormat      = null;
    private       TimeZone         defaultTimeZone = null;
    private       Calendar         calendar        = null;
    private final String           EMPTY_STRING     = "  ";
    private       Timer            timer           = null;

    TimerTest( )
    {
        super( );
        setHorizontalAlignment(SwingConstants.CENTER);
        setText(EMPTY_STRING);
        setPreferredSize(new Dimension(400, 200));
        setMinimumSize(new Dimension(400, 200));
        defaultTimeZone = TimeZone.getDefault( );
        timer = new TimerTest.TimeLabelTimer( );
        timer.start( );
    }

    /**
     * Timer class that is called back at intervals to adjust the
     * seconds in time display
     */
    private class TimeLabelTimer extends Timer implements ActionListener
    {
        private final String           TIME_FORMAT     = " HH:mm:ss ";
        private       SimpleDateFormat timeFormat      = null;

        TimeLabelTimer( )
        {
            // first param is callback interval in milliseconds
            super(1000, null);   // call back every second
            addActionListener(this);

            timeFormat      = new SimpleDateFormat(TIME_FORMAT);
            timeFormat.setTimeZone(defaultTimeZone);
        }

        /**
         * update the time display
         */
        public void actionPerformed(ActionEvent e)
        {
            calendar = Calendar.getInstance(defaultTimeZone);
            setText(timeFormat.format(calendar.getTime( )));
        }
    }

    public static void main(String[ ] args)
    {
        JFrame frame = new JFrame("Test Timer");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.getContentPane( ).add(new TimerTest( ), BorderLayout.CENTER);
        frame.setSize(400, 200);
        frame.pack( );
        frame.setVisible(true);
    }
}
(Review ID: 135746)
==========================================================================================================================================

Name: ns68243			Date: 06/26/2001

Remove SQE-lib-0121


======================================================================

Name: boT120536			Date: 07/31/2001


(Review ID: 126004)
======================================================================

Name: ddT132432			Date: 11/30/2001


Webbugs Review <--------------------------

The users test case shows his error as expected. When changing the 
system clock, the java application froze. I have tested this test case
on Win NT and Win 2k using 1.3.1, 1.3.1,  1.4beta3 and 1.4-rc-b87

###@###.###  2001-11-30
(Review ID: 135746)
======================================================================
JPE modified 

Add Yokogawa Electric in Customer Call section

###@###.### 2003-10-07
=====================================================================
JPE Modified 

Add Yokogawa another entry. thi occurs in Tiger.

java version "1.5.0-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta-b22)
Java HotSpot(TM) Client VM (build 1.5.0-beta-b22, mixed mode)

The licensee's Java product is to monitor the status of product line
in Factory and display clock.
To synchronize the monitor(clientPC) clock with the machine in the line, 
the system clock is adjusted periodically. 
After this synchronization, the clock seems to stop by this bug.

They want us to fix in Tiger and backport the fix into 1.4.X.

###@###.### 2003-10-08
=====================================================================
###@###.### 11/1/04 23:01 GMT

Comments
There's some serious confusion about the various timer implementations in the JDK. The summary and component refer to java.util.Timer, but the test program is all about javax.swing.Timer! And of course there's ScheduledThreadPoolExecutor, which inspired fixes to javax.swing.Timer but java.util.Timer has been left unchanged, still using System.currentTimeMillis for everything. Realistically, it's too late to fix java.util.Timer, and javax.swing.Timer has already been fixed, but Josh's comments from 2004 are still food for thought, which keeps me from closing this ancient bug.
22-09-2015

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mustang
02-10-2004

EVALUATION Thanks to JSR-166, it is now practical to fix this bug on most (if not all) platforms. The new System.nanoTime() method provides precisely the required functionality. It is not, however, sufficient to use a single queue based on relative time (as returned by System.nanoTime): This approach does not provide accurate absolute-time events. It is currently possible to use Timer to write a "personal information manager" that schedules events far in the future. If we switched to a single queue based on relative time, such an application would break. Simply put, it is required that: - Absolute times must track changes to the system clock. - Relative times must be impervious to changes in the system clock. The solution is to maintain two priority queues, one indexed by "absolute time" (currentTimeMillis), the other indexed by nanoTime. When an event is scheduled with an absolute time, put it in the former queue; when it's scheduled without an absolute time put it in the latter queue. Whenever a periodic event is scheduled for its second or subsequent execution, put it into the latter queue. (The intuition is that all such events are scheduled by relative time, even if the first event in the sequence was scheduled by absolute time.) A single thread monitors both queues. When it waits, it waits for the minimum time indicated by the head element of both queues. This dual-queue approach provides reliable periodic events and absolute-time events. To answer scheduledExecutionTime queries, it must be possible to tell by looking at a TimerTask which queue it is on (to properly interpret the long nextExecutionTime field. (Recall that timer tasks have no link back to their timers.) This means that we have to steal a bit somewhere. Luckily TimerTask has a 32-bit state field, of which two bits are currently in use. A minor deficiency of this implementation is that if the "absolute time" clock is set far backwards and the previous wait of the timer thread was for an event on the former (currentTimeMillis) queue, the Timer thread will oversleep and execute the event late by the amount that the clock was set back. The solution to this (which I'm not sure is worth implementing) is to use a slightly more complex algorithm to calculate the wait timeout. Instead of min(currentTimeMillis - head of absolute time queue, nanoTime - head of relative time queue), wait for min(min(currentTimeMillis - head of absolute time queue, SOME_CONSTANT), nanoTime - head of relative time queue). In other words, never wait longer than SOME_CONSTANT for an "absolute time" event. This bounds the tardiness in the face of arbitrary clock setbacks. (The bound need not be applied if there are no tasks on the absolute time queue.) Unfortunately, this fix requires a slight change to the semantics of certain methods. The current API specifies what constitutes legal delays and periods in terms of System.currentTimeMillis. This would have to be changed in order to adopt the above implementation strategy. It wouldn't break any real applications (which tend not to schedule events 200 years in the future), but might break some test code. ###@###.### 2004-04-13
13-04-2004