Author: jukka
Date: Wed Mar 12 15:41:42 2014
New Revision: 1576788

URL: http://svn.apache.org/r1576788
Log:
OAK-1534: Clock improvements

Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java?rev=1576788&r1=1576787&r2=1576788&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
 Wed Mar 12 15:41:42 2014
@@ -16,30 +16,117 @@
  */
 package org.apache.jackrabbit.oak.stats;
 
+import java.util.Date;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Mechanism for keeping track of time at millisecond accuracy.
  */
-public interface Clock {
+public abstract class Clock {
 
     /**
-     * Returns the current time in milliseconds.
+     * Millisecond granularity of the {@link #ACCURATE} clock.
+     * Configurable by the "accurate.clock.granularity" system property
+     * to make it easier to test the effect of a slow-moving clock on
+     * code that relies on millisecond timestamps.
+     */
+    private static final long ACCURATE_CLOCK_GRANULARITY =
+            Long.getLong("accurate.clock.granularity", 1);
+
+    private long monotonic = 0;
+
+    private long increasing = 0;
+
+    /**
+     * Returns the current time in milliseconds since the epoch.
      *
      * @see System#currentTimeMillis()
-     * @return current time in milliseconds
+     * @return current time in milliseconds since the epoch
      */
-    long getTime();
+    public abstract long getTime();
+
+    /**
+     * Returns a monotonically increasing timestamp based on the current time.
+     * A call to this method will always return a value that is greater than
+     * or equal to a value returned by any previous call. This contract holds
+     * even across multiple threads and in cases when the system time is
+     * adjusted backwards. In the latter case the returned value will remain
+     * constant until the previously reported timestamp is again reached.
+     *
+     * @return monotonically increasing timestamp
+     */
+    public synchronized long getTimeMonotonic() {
+        long now = getTime();
+        if (now > monotonic) {
+            monotonic = now;
+        } else {
+            now = monotonic;
+        }
+        return now;
+    }
+
+    /**
+     * Returns a strictly increasing timestamp based on the current time.
+     * This method is like {@link #getTimeMonotonic()}, with the exception
+     * that two calls of this method will never return the same timestamp.
+     * Instead this method will explicitly wait until the current time
+     * increases beyond any previously returned value. Note that the wait
+     * may last long if this method is called frequently from many concurrent
+     * thread or if the system time is adjusted backwards. The caller should
+     * be prepared to deal with an explicit interrupt in such cases.
+     *
+     * @return strictly increasing timestamp
+     * @throws InterruptedException if the wait was interrupted
+     */
+    public synchronized long getTimeIncreasing() throws InterruptedException {
+        long now = getTime();
+        while (now <= increasing) {
+            wait(0, 100000); // 0.1ms
+            now = getTime();
+        }
+        increasing = now;
+        return now;
+    }
+
+    /**
+     * Convenience method that returns the {@link #getTime()} value
+     * as a {@link Date} instance.
+     *
+     * @return current time
+     */
+    public Date getDate() {
+        return new Date(getTime());
+    }
+
+    /**
+     * Convenience method that returns the {@link #getTimeMonotonic()} value
+     * as a {@link Date} instance.
+     *
+     * @return monotonically increasing time
+     */
+    public Date getDateMonotonic() {
+        return new Date(getTimeMonotonic());
+    }
+
+    /**
+     * Convenience method that returns the {@link #getTimeIncreasing()} value
+     * as a {@link Date} instance.
+     *
+     * @return strictly increasing time
+     */
+    public Date getDateIncreasing() throws InterruptedException {
+        return new Date(getTimeIncreasing());
+    }
 
     /**
      * Simple clock implementation based on {@link System#currentTimeMillis()},
      * which is known to be rather slow on some platforms.
      */
-    Clock SIMPLE = new Clock() {
+    public static Clock SIMPLE = new Clock() {
         @Override
         public long getTime() {
-            return System.currentTimeMillis();
+            return System.currentTimeMillis() & ~0xfL;
         }
     };
 
@@ -49,21 +136,37 @@ public interface Clock {
      * time based on occasional calls to {@link System#currentTimeMillis()}
      * to prevent clock drift.
      */
-    Clock ACCURATE = new Clock() {
+    public static Clock ACCURATE = new Clock() {
         private static final long NS_IN_MS = 1000000;
-        private static final long NS_IN_S = NS_IN_MS * 1000;
-        private volatile long ms = System.currentTimeMillis();
-        private volatile long ns = System.nanoTime();
+        private static final long SYNC_INTERVAL = 1000; // ms
+        private long ms = SIMPLE.getTime();
+        private long ns = System.nanoTime();
         @Override
-        public long getTime() {
-            long diff = System.nanoTime() - ns;
-            if (diff < NS_IN_S) {
-                return ms + diff / NS_IN_MS;
-            } else {
-                ms = System.currentTimeMillis();
+        public synchronized long getTime() {
+            long nsIncrease = Math.max(System.nanoTime() - ns, 0); // >= 0
+            long msIncrease = nsIncrease / NS_IN_MS;
+            if (ACCURATE_CLOCK_GRANULARITY > 1) {
+                msIncrease -= msIncrease % ACCURATE_CLOCK_GRANULARITY;
+            }
+
+            long now = ms + msIncrease;
+            if (now > ms + SYNC_INTERVAL) {
+                ms = SIMPLE.getTime();
                 ns = System.nanoTime();
-                return ms;
+                long jump = ms - now;
+                if (jump != 0 && Math.abs(jump) < SYNC_INTERVAL) {
+                    // currentTimeMillis() jumped a little bit, which was
+                    // probably caused by its lower granularity instead of
+                    // an adjustment to system time, so we reduce the jump
+                    // to just 0.5ms to make the reported time smoother
+                    ms = now;
+                    ns += Long.signum(jump) * NS_IN_MS / 2;
+                } else {
+                    now = ms;
+                }
             }
+
+            return now;
         }
     };
 
@@ -72,7 +175,7 @@ public interface Clock {
      * instantaneously thanks to a background task that takes care of the
      * actual time-keeping work.
      */
-    public static class Fast implements Clock {
+    public static class Fast extends Clock {
 
         private volatile long time = ACCURATE.getTime();
 

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java?rev=1576788&r1=1576787&r2=1576788&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
 Wed Mar 12 15:41:42 2014
@@ -52,4 +52,28 @@ public class ClockTest {
         }
     }
 
+    @Test
+    public void testClockIncreasing() throws InterruptedException {
+        ScheduledExecutorService executor =
+                Executors.newSingleThreadScheduledExecutor();
+        try {
+            Clock[] clocks = new Clock[] {
+                    Clock.SIMPLE,
+                    Clock.ACCURATE,
+                    new Clock.Fast(executor)
+            };
+
+            long[] time = new long[clocks.length];
+            for (int i = 0; i < 10; i++) {
+                for (int j = 0; j < clocks.length; j++) {
+                    long now = clocks[j].getTimeIncreasing();
+                    assertTrue(time[j] < now);
+                    time[j] = now;
+                }
+            }
+        } finally {
+            executor.shutdown();
+        }
+    }
+
 }
\ No newline at end of file


Reply via email to