Java bug in sleep() when changing OS time : any workaround?
The bug that annoys me is the same than this ticket. Basically, if you change the OS clock to a date in the past, all the thread that were sleeping at the time of the change won't wake up.
The application I am developping is meant to be running 24/24, and we would like to be able to change the OS date without stopping it (for example, to switch from summer time to winter time). What happens for the moment is that when we change the date to the past, then some parts of the application just freeze. I observed that on multiple machine, on Windows XP and Linux 2.6.37, and with a recent JVM (1.6.0.22).
I tried many Java sleeping primitives, but they all have the same behavior :
- Thread.sleep(long)
- Thread.sleep(long, int)
- Object.wait(long)
- Object.wait(long, int)
- Thread.join(long)
- Thread.join(long, int)
- LockSupport.parkNanos(long)
- java.util.Timer
- javax.swing.Timer
Now, I am out of idea to work around this problem. I think there is nothing I can do to prevent the sleeping threads to freeze. But I would like, at least, to warn the user when a dangerous system clock change is detected.
I came up with a monitoring thread that detects such changes :
Thread t = new Thread(new Runnable() {
@Override
public void run() {
long ms1 = System.currentTimeMillis();
long ms2;
while(true) {
ms2 = ms1;
ms1 = System.currentTimeMillis();
if (ms1 < ms2) {
warnUserOfPotentialFreeze();
}
Thread.yield();
}
}
});
t.setName("clock monitor");
t.setPriority(Thread.MIN_PRIORITY);
t.setDaemon(true);
t.start();
The problem is that this makes the application grow from 2% CPU usage to 15% when idle.
Do you have an idea to work around the original problem, or can you think of another开发者_开发问答 way to monitor the appearance of thread freeze ?
Edit
Ingo suggested not to touch the system clock. I agree that it's generally not needed. The problem is that we don't control what our clients do with their computers (we plan to sell hundred of copies).
Worse : one of our machine exhibits this problem without any manual intervention. I guess the OS (Windows XP) regularly synchronizes its clock to the RTC clock, and this makes the OS clock go back in time naturally.
Epilogue
I found out that some statements in my question were wrong. There are actually two separate causes involved in my initial problem. Now, I can say two things for sure :
On my machine only (archlinux with kernel 2.6.37 with an OpenJDK 64 bits 1.6.0_22),
Thread.sleep
,Object.wait
,Thread.join
,LockSupport.parkNanos
have the same problem : they wake up only when the system clock reaches the "target" time of awakening. However, a simplesleep
in my shell does not exhibit the problem.On all the machines I tested (included mine),
java.util.Timer
andjava.swing.Timer
have the same problem (they are blocked until the "target" time is reached).
So, what I've done is that I replaced all the java's Timer
by a simpler implementation. This solves the problem for all the machines but mine (I just hope my machine is an exception more than a rule).
According to the bug ticket, your threads aren't frozen, they will resume once the clock catches up to where it was before it was modified (so if they moved it back an hour, your threads will resume in 1 hour).
Of course, that is still not very useful. The root cause seems to be that Thread.sleep()
resolves to a system call that puts the thread to sleep until some specific timestamp in the future, rather than for a specified duration. To work around it you would need to implement your own version of Thread.sleep()
that uses System.nanoTime()
instead of System.currentTimeMillis()
or any other time-dependent API. How to do that without using the built-in Thread.sleep()
I can't say, however.
Edit:
Or, what if you create some external app in another language (like C or whatever else you prefer) that does nothing but wait for a specified duration and then exit. Then instead of calling Thread.sleep() in Java, you can spawn a new instance of this external process, and then call waitFor() on it. This will "sleep" the Java thread for all practical purposes, and so long as your external app is able to sleep for the correct duration, it will resume at the correct time without getting frozen and without thrashing the CPU.
Seems like a long way to go to fix the issue, but it's the only feasible workaround that I can think of. Also, given that spawning an external process is a relatively expensive operation, it probably works best if you are sleeping for a relatively long time (like several hundred ms or more). For shorter durations it might just continue thrashing the CPU.
As others have said, you definitely shouldn't have to change the system clock. The timestamp (milliseconds since the epoch) is consistent across all computers across the world, but the local time depends on your location, observation on Daylight Savings Time and so on. Therefore, the problem is with the OS locale and time/date settings.
(Still, I agree that if the system clock does change, the JVM should detect this and update or awaken sleeping threads to combat the problem.)
Please test the latest jre 1.7.0_60. It resolves the described problem caused by a system time shift to the past at least for systems with a glibc version released since 2009.
Related bug http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6900441 has been fixed and therefore all functions mentioned by you (Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos, java.util.Timer and java.swing.Timer
) should work as expected.
I have tested it with a linux kernel 3.7.10.
@Op. You have implemented something that looks like "busy waiting", and that will always consume lots of resources.
I agree with the others, I don't see why you need to change the system clock when you go from summer to winter time.
You don't change OS time for DST adjustments! It's nothing more than a Time Zone change. System clock should always be GMT. And the wall clock time that you display to the user is derived from that with the proper time zone offset.
精彩评论