Detect discontinuities in Windows clock in Java

Posted on

Problem

Please review the code below, which detects discontinuities in Windows system clock in long-running programs.

/*
 * Created on Apr 16, 2015
 *
 * The purpose of this class is to periodically check to see if the time of the Thread.sleep() agrees with the system clock.
 * If it does not, we need to send an interrupt to the ***.
 * 
 * This is because after Windows 7, Thread.sleep does not count down in S3 or S4 mode.
 * http://stackoverflow.com/questions/29394222/java-thread-sleep-on-windows-10-stops-in-s3-sleep-status
 * 
 */
package com.sengsational.clockchecker;

import java.io.IOException;
import java.net.Socket;
import java.util.Date;

public class ClockChecker implements Runnable{

    private static boolean runFlag = false;
    private static Thread runningThread;
    private static ClockChecker singleton;
    private static Thread runThread;
    private static boolean mSimulateClockProblem;
    public static long pollIntervalMs = 15000;
    public static final int ACCURACY_MS = 1000;
    
    public static ClockChecker getInstance(){
        if (singleton == null) {
            singleton = new ClockChecker();
        }
        return singleton;
    }
    
    private ClockChecker(){
        runningThread = new Thread(this);
        runningThread.start();
        try {Thread.sleep(100);} catch (Throwable t){};
    }    

    @Override
    public void run() {
        if (isRunning()) {
            System.out.println(new Date() + " ERROR: ClockChecker should only have one run thread.");
            return;
        } else {
            System.out.println(new Date() + " ClockChecker run() is starting.");
            setRunning(true);
        }
        long wakePoint = 0;
        long msAccuracy = 0;
        ClockChecker.runThread = Thread.currentThread();
        while (isRunning()) {
            try {
                wakePoint = ((new Date().getTime() + pollIntervalMs));
                Thread.sleep(pollIntervalMs); // Blocks
                if(ClockChecker.mSimulateClockProblem && Math.random() < 0.2d){
                    wakePoint = wakePoint - 2000;
                   System.out.println(new Date() + " Simulating a clock problem.");
                }
                msAccuracy = Math.abs(new Date().getTime() - wakePoint);
                if (msAccuracy > ACCURACY_MS) {
                    System.out.println(new Date() + " ClockChecker has detected a clock accuracy issue of " + msAccuracy + "ms.");
                    System.out.println(new Date() + " >> Here is where you alert your waiting tasks that depend upon wall-clock accuacy <<");
                }
            } catch (InterruptedException e) {
                System.out.println(new Date() + " ClockChecker run() has been interrupted.");
                setRunning(false);
            }
        } 
        System.out.println(new Date() + " ClockChecker run() is ending.");
        setRunning(false);
    }
    
    public synchronized boolean isRunning(){
        return runFlag;
    }
    
    public synchronized void setRunning(boolean running){
        ClockChecker.runFlag = running;
    }
    
    private static void setSimulateClockProblem(boolean simulateClockProblem) {
        mSimulateClockProblem = simulateClockProblem;
    }

    public static void shutDown() {
        runThread.interrupt();
        try {Thread.sleep(100);} catch (Throwable t){}
    }

    public static void main(String[] args) throws InterruptedException {
        
        System.out.println("nMAIN: Simulate starting the main app.");
        ClockChecker.getInstance();
        ClockChecker.setSimulateClockProblem(false);  // Set to true on Windows 7 to have a roughly 40% chance of simulating a clock discontinuity.
        System.out.println("nMAIN: Now we have a ClockChecker object. Put Win10 machine into S3 sleep mode NOW!" +
                           "n      (you have fewer than 30 seconds to do so).  Leave in S3 for 1 or more minutes.");
        Thread.sleep(30 * 1000);
        System.out.println("nMAIN: Examine the above messages (if any) about clock accuracy after S3 sleep.");
        System.out.println("nMAIN: We will now shutdown.");
        ClockChecker.shutDown();  
        System.out.println(new Date() + " ClockChecker main() ending.");
    }
}

By way of background, when running on Windows versions 8 and 10, but not earlier versions, a Java Thread.sleep(X) will sleep for X milliseconds, plus the number of milliseconds the machine has been in S3 or S4 sleep. In other words, when Windows is awake again, it will act as if no time has passed, thus any program requiring a linkage to wall clock time will need a fix. I would like to understand if there are any ideas to improve the above work-around to this problem.

Solution

Welcome to codereview.se and thanks for sharing your code.

Here is what I think about it. Please keep in mind, that this is my personal view on it.

Do not use the Java Singelton pattern.

The singelton pattern is just another name for a global variable, which we consider harmful and bad coding since the early days of programming.

Also the pattern is not that easy to implement correctly and so even your approach is not thread safe.

For more on this an the static access the singelton pattern requires read: https://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/

Do not do any work in constructors

Constructors should only assign its parameters to member variables. Any validation on the parameters should be done before calling the constructor in the first place.

Also do not instantiate other classes inside the constructor since this is another bad case of static access which makes your code hard to extend, reuse and test.

Especially the constructor should not pass the the current object to any other method since the object is not fully configured yet until the constructor finished.

Do not have top level classes implementing simple (functional) interfaces.

This habit vanishes the possibility to separate glue code from the actual business code.

Leave a Reply

Your email address will not be published. Required fields are marked *