All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.epam.deltix.util.time.TimeKeeper Maven / Gradle / Ivy

/*
 * Copyright 2021 EPAM Systems, Inc
 *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership. Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.epam.deltix.util.time;

import com.epam.deltix.gflog.api.Log;
import com.epam.deltix.gflog.api.LogFactory;
import com.epam.deltix.util.lang.MathUtil;
import com.epam.deltix.util.lang.Util;

import java.util.EmptyStackException;
import java.util.PriorityQueue;
import java.util.Stack;
import java.util.concurrent.locks.LockSupport;

/**
 *
 */
public class TimeKeeper extends Thread {
    private static final Log LOG = LogFactory.getLog(TimeKeeper.class);
    public static void          main (String [] args) throws Exception {
        TimeKeeper.setMode (Mode.HIGH_RESOLUTION_SYNC_BACK);
        
        for (;;) {
            LockSupport.parkNanos (235327881);
            
            long        keepTime = TimeKeeper.currentTime;
            long        sysTime = System.currentTimeMillis ();
            
            System.out.print (GMT.formatDateTimeMillis (sysTime));
            
            if (sysTime == keepTime)
                System.out.println (": IN SYNC");
            else if (sysTime < keepTime)
                System.out.println (": Keeper is ahead by " + (keepTime - sysTime) + "ms");
            else
                System.out.println (": Keeper is behind by " + (sysTime - keepTime) + "ms");                                              
        }
    }
    
    
    private static final long           NOT_SCHEDULED = Long.MAX_VALUE;
    private static final boolean        DEBUG = false;
    private static final double         DISTORTION = 1;
    private static final long           IN_SYNC = -1;
    private static final long           RUNAWAY_THRESHOLD_MS = 1000;
    private static final long           M = 1000000;
    private static final long           PARK_LOW = 500000;
    private static final long           PARK_MEDIUM = 100000;
    private static final long           START_TIME = System.currentTimeMillis ();

    public enum Mode {
        /**
         *  Park for 0.5ms between time maintenance attempts. 
         *  This produces about 1ms accuracy.
         */
        LOW_RESOLUTION,
        
        /**
         *  Park for 20us between time maintenance attempts. 
         *  This produces about good accuracy on Linux but only
         *  about 1ms accuracy on Windows.
         */
        MEDIUM_RESOLUTION,
        
        /**
         *  Consume a CPU core (one per JVM process), but produce very
         *  precise time values.
         */
        HIGH_RESOLUTION_SYNC_BACK
    }
    
    private static abstract class Task implements Runnable, Comparable  {
        private long    scheduledAtNanos = NOT_SCHEDULED;

        public final boolean    isScheduled () {
            return (INSTANCE.isScheduled (this));
        }

        public final int        compareTo (Task o) {
            return (MathUtil.compare (scheduledAtNanos, o.scheduledAtNanos));
        }

        public final boolean    scheduleAt (long ticks) {
            return (INSTANCE.schedule (this, ticks));
        }

        public final boolean    cancel () {
            return (INSTANCE.cancel (this));
        }
    }

    private static final class UnparkTask extends Task {
        private Thread              thread;

        public void             run () {
            LockSupport.unpark (thread);

            thread = null;

            unparkTaskPool.push (this);
        }
    }
        
    public static volatile long                 currentTime = START_TIME;
    public static volatile long                 currentTimeNanos = START_TIME * M;

    private static volatile Mode                mode = Mode.LOW_RESOLUTION;
    
    private long                                lastTimeMillis = START_TIME;
    private boolean                             setBackReported = false;
    private long                                offset;
    private long                                runawayAt = IN_SYNC;
    private static final Stack      unparkTaskPool = 
        new Stack  ();

    private final PriorityQueue  taskQueue = new PriorityQueue  ();

    private boolean                 isScheduled (Task task) {
        synchronized (taskQueue) {
            return (task.scheduledAtNanos != Long.MAX_VALUE);
        }
    }

    private boolean                 schedule (Task task, long ticks) {
        if (ticks == Long.MAX_VALUE)
            throw new IllegalArgumentException ("ticks == NOT_SCHEDULED");

        synchronized (taskQueue) {
            boolean     isNew = task.scheduledAtNanos == NOT_SCHEDULED;

            if (!isNew) {
                boolean     ok = taskQueue.remove (task);

                assert ok;
            }

            task.scheduledAtNanos = ticks;

            taskQueue.offer (task);

            return (isNew);
        }
    }

    private boolean                 cancel (Task task) {
        synchronized (taskQueue) {
            if (task.scheduledAtNanos == NOT_SCHEDULED)
                return (false);

            boolean     ok = taskQueue.remove (task);

            assert ok;

            task.scheduledAtNanos = NOT_SCHEDULED;

            return (true);
        }
    }

    private static final TimeKeeper    INSTANCE = new TimeKeeper ();

    private static volatile long        sleepUntilLateness = 0;
    
    private static long     nanoTime () {
        long                    nanoTime = System.nanoTime ();

        if (!DEBUG)
            return (nanoTime);

        return ((long) (nanoTime * DISTORTION));
    }
    
    private TimeKeeper () {
        super ("TimeKeeper");

        final long              nanoTime = nanoTime ();

        lastTimeMillis = System.currentTimeMillis ();
        offset = lastTimeMillis * M - nanoTime;                
    }

    /*
        Returns true if current time goes forward
     */
    private boolean         getSystemTimeNoRollBack () {
        final long              t = System.currentTimeMillis ();

        if (t == lastTimeMillis)
            return (false);

        if (t < lastTimeMillis) {
            //
            //  System clock was set back (usually by NTP)
            //
            if (!setBackReported) {
                LOG.warn ("System time was adjusted from %s -> %s").with(GMT.formatDateTimeMillis (t)).with(GMT.formatDateTimeMillis (lastTimeMillis));

                setBackReported = true;
            }

            return (false);
        }
        else {
            //
            //  System clock just went forward. We trust it right now
            //  (for about 1 microsecond).
            //
            setBackReported = false;
        }

        lastTimeMillis = t;
        return (true);
    }

    private boolean         isRunawayConfirmed () {
        return (runawayAt != IN_SYNC && lastTimeMillis >= runawayAt);
    }
    
    private void            set (long nanoTime) {
        assert nanoTime >= currentTimeNanos;
        assert !isRunawayConfirmed ();    // Else we should not be increasing.

        currentTimeNanos = nanoTime;
        currentTime = nanoTime / M;
    }

    /**
     *  This method performs time-keeping functions.
     *
     *  timeNanos = nanoTime + offset
     */
    private void            doAccurateTimeMaintenance () {
        long                    sysNanoTime = nanoTime ();
        long                    cpuTimeNanos = sysNanoTime + offset;
        boolean                 sysClockChanged = getSystemTimeNoRollBack ();        

        if (!sysClockChanged && !isRunawayConfirmed ()) {  
            // Keep ticking
            set (cpuTimeNanos);
            return;
        }

        long                    cpuTimeMillis = cpuTimeNanos / M;
        long                    keeperAhead = cpuTimeMillis - lastTimeMillis;

        if (keeperAhead < 0) {
            runawayAt = IN_SYNC;
            //
            //  Assumption: system clock is never early. Therefore,
            //  adjust offset just enough so that model time catches up right away
            //
            cpuTimeNanos = lastTimeMillis * M;

            if (DEBUG) {
                System.out.printf (
                    "TK: + %,d ns\n",
                    cpuTimeNanos - sysNanoTime - offset
                );
            }

            offset = cpuTimeNanos - sysNanoTime;

            set (cpuTimeNanos);
            return;
        }

        if (keeperAhead == 0) {
            runawayAt = IN_SYNC;
            set (cpuTimeNanos);
            return;
        }
        //
        //  keeper is ahead by at least 1ms
        //
        if (runawayAt == IN_SYNC) {
            if (DEBUG) {
                System.out.print (
                    "TK: Ahead, raising runaway suspicion\n"                    
                );
            }
            
            runawayAt = lastTimeMillis + RUNAWAY_THRESHOLD_MS;
        }
        
        if (lastTimeMillis >= runawayAt) {
            //
            //  Keeper has been consistently ahead for RUNAWAY_THRESHOLD_MS.
            //  Keeper time can never go back, however.
            //
            long        maxCompliantTimeNanos = lastTimeMillis * M + (M - 1);
            long        minPossibleOffset = currentTimeNanos - sysNanoTime;
            long        targetOffset = maxCompliantTimeNanos - sysNanoTime;

            if (targetOffset >= minPossibleOffset) {
                if (DEBUG) {
                    System.out.printf (
                        "TK: -%,d (FINAL)\n",
                        offset - targetOffset
                    );
                }
                
                offset = targetOffset;
                runawayAt = IN_SYNC;
                
                set (maxCompliantTimeNanos);
            }
            else {
                if (DEBUG) {
                    System.out.printf (
                        "TK: STAYING PUT; -%,d (STILL RUNAWAY)\n", 
                        offset - minPossibleOffset
                    );
                }
                
                offset = minPossibleOffset;                          
            }
        }
        else
            set (cpuTimeNanos);
    }

    private void            doApproximateTimeMaintenance () {
        if (getSystemTimeNoRollBack ()) {
            currentTime = System.currentTimeMillis ();
            currentTimeNanos = currentTime * M;
        }
    }

    private void            runTasks () {
        for (;;) {
            Task            task;

            synchronized (taskQueue) {
                task = taskQueue.peek ();

                if (task == null)
                    break;

                if (currentTimeNanos < task.scheduledAtNanos)
                    break;

                Task        check = taskQueue.poll ();

                assert check == task;

                task.scheduledAtNanos = NOT_SCHEDULED;
            }

            task.run ();
        }
    }

    @Override
    public void             run () {
        int         exceptionCount = 0;

        for (;;) {
            try {
                switch (mode) {
                    case HIGH_RESOLUTION_SYNC_BACK:
                        doAccurateTimeMaintenance ();
                        break;
                        
                    case LOW_RESOLUTION:
                        doApproximateTimeMaintenance ();
                        LockSupport.parkNanos (PARK_LOW);
                        break;
                        
                    case MEDIUM_RESOLUTION:
                        doAccurateTimeMaintenance ();
                        LockSupport.parkNanos (PARK_MEDIUM);
                        break;
                }

                runTasks ();
            } catch (Exception x) {
                if (exceptionCount++ > 500) {
                    LOG.error("TimeKeeper has logged 500 errors. Shutting down.");
                    System.exit (1);
                }

                LOG.error("Exception in TimeKeeper: %s").with(x);
            }
        }
    }

    static {
        if (Util.IS_WINDOWS_OS) {
            //
            // Force the Windows system clock into fast mode
            // Workaround per   http://bugs.sun.com/view_bug.do?bug_id=6435126
            //
            Thread  magic =
                new Thread ("Windows System Clock Speeder-Upper") {
                    @Override
                    @SuppressWarnings ("SleepWhileInLoop")
                    public void run() {
                        for (;;) {
                            try {
                                Thread.sleep (Integer.MAX_VALUE);
                            } catch(InterruptedException ex) {
                            }
                        }
                    }
                };

            magic.setDaemon (true);
            magic.start ();
        }

        INSTANCE.setDaemon (true);
        INSTANCE.start ();
    }
    //
    //  PUBLIC API
    //
    public static void      parkNanos (long nanos) {
        switch (mode) {
            case HIGH_RESOLUTION_SYNC_BACK: 
                parkUntil (currentTimeNanos + nanos);
                break;
                
            default:
                LockSupport.parkNanos (nanos);
                break;
        }                
    }

    public static void      parkUntil (long nanoTime) {
        UnparkTask  u;

        try {
            u = unparkTaskPool.pop ();
        } catch (EmptyStackException x) {
            u = new UnparkTask ();
        }

        u.thread = Thread.currentThread ();

        boolean       ha = false;
        
        switch (mode) {
            case HIGH_RESOLUTION_SYNC_BACK: 
                ha = true;
                break;
        }
        
        long                heuristicDeadline = nanoTime;

        if (ha)
            heuristicDeadline -= (sleepUntilLateness + 30);

        u.scheduleAt (heuristicDeadline);

        LockSupport.park ();

        if (ha) {
            final long        observedLateness = currentTimeNanos - nanoTime;
            //
            //  The following is not synchronized, but should be adequate (and safe).
            //
            final long        v = sleepUntilLateness;
            //
            //  Move by only 1 tick at a time, no matter what magnitude.
            //
            if (observedLateness < v)
                sleepUntilLateness = v - 1;
            else if (observedLateness > sleepUntilLateness)
                sleepUntilLateness = v + 1;
        }
    }

    public static Mode          getMode () {
        return mode;
    }

    public static void          setMode (Mode flag) {
        if (flag == null)
            throw new IllegalArgumentException ("null");
        
        mode = flag;
    }
    
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy