summaryrefslogtreecommitdiffstats
path: root/vespajlib/src/main/java/com/yahoo/time/WallClockSource.java
blob: 5fef1f94879466f67cc3830c3596f7d9c347e604 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.time;

import com.google.common.annotations.Beta;

/**
 * A source for high-resolution timestamps.
 *
 * @author arnej27959
 */

@Beta
public class WallClockSource {

    private volatile long offset;

    /**
     * Obtain the current time in nanoseconds.
     * The epoch is January 1, 1970 UTC just as for System.currentTimeMillis(),
     * but with greater resolution.  Note that the absolute accuracy may be
     * no better than currentTimeMills().
     * @return nanoseconds since the epoch.
     **/
    public final long currentTimeNanos() {
        return System.nanoTime() + offset;
    }

    /**
     * Create a source with 1 millisecond accuracy at start.
     **/
    WallClockSource() {
        long actual = System.currentTimeMillis();
        actual *= 1000000;
        this.offset = actual - System.nanoTime();
        initialAdjust();
    }

    /** adjust the clock source from currentTimeMillis()
     * to ensure that it is no more than 1 milliseconds off.
     * @return true if we want adjust called again soon
     **/
    boolean adjust() {
        long nanosB = System.nanoTime();
        long actual = System.currentTimeMillis();
        long nanosA = System.nanoTime();
        if (nanosA - nanosB > 100000) {
            return true; // not a good time to adjust, try again soon
        }
        return adjustOffset(nanosB, actual, nanosA);
    }

    private boolean adjustOffset(long before, long actual, long after) {
        actual *= 1000000; // convert millis to nanos
        if (actual > after + offset) {
            // System.out.println("WallClockSource adjust UP "+(actual-after-offset));
            offset = actual - after;
            return true;
        }
        if (actual + 999999 < before + offset) {
            // System.out.println("WallClockSource adjust DOWN "+(before+offset-actual-999999));
            offset = actual + 999999 - before;
            return true;
        }
        return false;
    }

    private void initialAdjust() {
       for (int i = 0; i < 100; i++) {
           long nanosB = System.nanoTime();
           long actual = System.currentTimeMillis();
           long nanosA = System.nanoTime();
           adjustOffset(nanosB, actual, nanosA);
       }
    }


    static private WallClockSource autoAdjustingInstance = new WallClockSource();

    /**
     * Get a WallClockSource which auto adjusts to wall clock time.
     **/
    static public WallClockSource get() {
        autoAdjustingInstance.startAdjuster();
        return autoAdjustingInstance;
    }

    private Thread adjuster;

    private synchronized void startAdjuster() {
        if (adjuster == null) {
            adjuster = new AdjustThread();
            adjuster.setDaemon(true);
            adjuster.start();
        }
    }

    private class AdjustThread extends Thread {
        public void run() {
            int millis = 0;
            int nanos = 313373; // random number
            while (true) {
                try {
                    sleep(millis, nanos);
                    if (++millis > 4321) {
                        millis = 1000; // do not sleep too long
                    }
                } catch (InterruptedException e) {
                    return;
                }
                if (adjust()) {
                    // adjust more often in case clock jumped
                    millis = 0;
                }
            }
        }
    }

}