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