This is an automated email from the ASF dual-hosted git repository.
reschke pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 06d46de56a OAK-11622: Clock improvements (#2204)
06d46de56a is described below
commit 06d46de56a4e28aadeff375bf3b95ac39869311b
Author: Julian Reschke <[email protected]>
AuthorDate: Fri Apr 4 10:30:40 2025 +0200
OAK-11622: Clock improvements (#2204)
---
.../org/apache/jackrabbit/oak/stats/Clock.java | 75 +++++++++++++++++-----
.../apache/jackrabbit/oak/stats/package-info.java | 2 +-
.../org/apache/jackrabbit/oak/stats/ClockTest.java | 68 +++++++++++++++++++-
.../jackrabbit/oak/stats/NonTickingTestClock.java} | 20 +++++-
4 files changed, 145 insertions(+), 20 deletions(-)
diff --git
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
index a19c60e595..87e3a20c3c 100644
--- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
+++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/Clock.java
@@ -16,7 +16,10 @@
*/
package org.apache.jackrabbit.oak.stats;
+import org.apache.jackrabbit.oak.commons.properties.SystemPropertySupplier;
+
import java.io.Closeable;
+import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Date;
@@ -29,7 +32,12 @@ import java.util.concurrent.atomic.AtomicLong;
/**
* Mechanism for keeping track of time at millisecond accuracy.
* <p>
- * As of Oak 1.20, this extends from {@link java.time.Clock}.
+ * This extends from {@link java.time.Clock}. Consumers of clocks
+ * are advised to use that interface, unless the additional features
+ * of this class are needed.
+ * <p>
+ * Note that implementations of this class in general do not support
+ * the timezone related features of {@linkplain java.time.Clock}.
*/
public abstract class Clock extends java.time.Clock {
@@ -40,7 +48,7 @@ public abstract class Clock extends java.time.Clock {
* the effect of an inaccurate system clock.
*/
private static final int SIMPLE_CLOCK_NOISE =
- Integer.getInteger("simple.clock.noise", 0);
+ SystemPropertySupplier.create("simple.clock.noise", 0).get();
/**
* Millisecond granularity of the {@link #ACCURATE} clock.
@@ -49,15 +57,15 @@ public abstract class Clock extends java.time.Clock {
* code that relies on millisecond timestamps.
*/
private static final long ACCURATE_CLOCK_GRANULARITY =
- Long.getLong("accurate.clock.granularity", 1);
+ SystemPropertySupplier.create("accurate.clock.granularity",
1L).get();
/**
* Millisecond update interval of the {@link Fast} clock. Configurable
- * by the "fast.clock.interval" system property to to make it easier
+ * by the "fast.clock.interval" system property to make it easier
* to test the effect of different update frequencies.
*/
static final long FAST_CLOCK_INTERVAL =
- Long.getLong("fast.clock.interval", 10);
+ SystemPropertySupplier.create("fast.clock.interval", 10L).get();
private long monotonic = 0;
@@ -65,6 +73,10 @@ public abstract class Clock extends java.time.Clock {
/**
* Returns the current time in milliseconds since the epoch.
+ * <p>
+ * Users of this class should use {@link java.time.Clock#millis()} instead.
+ * <em>This</em> abstract method remains here for cases where this
+ * class is extended.
*
* @see System#currentTimeMillis()
* @see java.time.Clock#millis()
@@ -72,6 +84,17 @@ public abstract class Clock extends java.time.Clock {
*/
public abstract long getTime();
+ /**
+ * Returns the current time in milliseconds since the epoch.
+ *
+ * @see System#currentTimeMillis()
+ * @return current time in milliseconds since the epoch
+ */
+ @Override
+ public long millis() {
+ return 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
@@ -96,7 +119,7 @@ public abstract class Clock extends java.time.Clock {
* 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
+ * 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
@@ -161,6 +184,32 @@ public abstract class Clock extends java.time.Clock {
}
}
+ /**
+ * Waits for the given delta in ms. The current thread
+ * is suspended until the {@link #getTimeIncreasing()} method returns
+ * a time that's equal or greater than the start time plus the given
+ * delta.
+ *
+ * @param delta time in milliseconds to wait for
+ * @throws InterruptedException if the wait was interrupted
+ */
+ public void waitFor(long delta) throws InterruptedException {
+ waitUntil(getTime() + delta);
+ }
+
+ /**
+ * Waits for the given duration. The current thread
+ * is suspended until the {@link #getTimeIncreasing()} method returns
+ * a time that's equal or greater than the start time plus the given
+ * delta.
+ *
+ * @param delta Duration to wait for
+ * @throws InterruptedException if the wait was interrupted
+ */
+ public void waitFor(Duration delta) throws InterruptedException {
+ waitUntil(getTime() + delta.toMillis());
+ }
+
@Override
public ZoneId getZone() {
return ZoneId.of("Z");
@@ -273,7 +322,7 @@ public abstract class Clock extends java.time.Clock {
// Last clock sync was over 10s ago or the nanosecond timer has
// drifted more than 100ms from the wall clock, so it's best to
- // to a hard sync with no smoothing.
+ // do a hard sync with no smoothing.
if (nowms >= ms + 1000) {
ms = nowms;
ns = nowns;
@@ -307,12 +356,8 @@ public abstract class Clock extends java.time.Clock {
private final ScheduledFuture<?> future;
public Fast(ScheduledExecutorService executor) {
- future = executor.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- time = ACCURATE.getTime();
- }
- }, FAST_CLOCK_INTERVAL, FAST_CLOCK_INTERVAL,
TimeUnit.MILLISECONDS);
+ future = executor.scheduleAtFixedRate(() ->
+ time = ACCURATE.getTime(), FAST_CLOCK_INTERVAL,
FAST_CLOCK_INTERVAL, TimeUnit.MILLISECONDS);
}
@Override
@@ -332,7 +377,7 @@ public abstract class Clock extends java.time.Clock {
/**
* A virtual clock that has no connection to the actual system time.
- * Instead the clock maintains an internal counter that's incremented
+ * Instead, the clock maintains an internal counter that's incremented
* atomically whenever the current time is requested. This guarantees
* that the reported time signal is always strictly increasing.
*/
@@ -357,5 +402,5 @@ public abstract class Clock extends java.time.Clock {
public String toString() {
return "Clock.Virtual";
}
- };
+ }
}
diff --git
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
index b04ab9e46d..b9c303d14a 100644
---
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
+++
b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.2.0")
+@Version("1.3.0")
package org.apache.jackrabbit.oak.stats;
import org.osgi.annotation.versioning.Version;
diff --git
a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
index 16acf31062..0b6dce9d6a 100644
--- a/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
+++ b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/ClockTest.java
@@ -19,6 +19,8 @@ package org.apache.jackrabbit.oak.stats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import java.time.Duration;
+import java.time.Instant;
import java.time.ZoneId;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -68,19 +70,83 @@ public class ClockTest {
}
@Test
- public void testClockJavaTime() throws InterruptedException {
+ public void testGetDate() {
+ Clock c = Clock.SIMPLE;
+
+ long t1 = c.millis();
+ long t2 = c.getDate().getTime();
+ long t3 = c.millis();
+
+ assertTrue(t1 <= t2);
+ assertTrue(t2 <= t3);
+ }
+
+ @Test
+ public void testWaitUntilSimple() throws InterruptedException {
+ testClockWaitUntil(Clock.SIMPLE);
+ testClockWaitFor(Clock.SIMPLE);
+ }
+
+ @Test
+ public void testWaitUntilAccurate() throws InterruptedException {
+ testClockWaitUntil(Clock.ACCURATE);
+ testClockWaitFor(Clock.ACCURATE);
+ }
+
+ @Test
+ public void testWaitUntilVirtual() throws InterruptedException {
+ testClockWaitUntil(new Clock.Virtual());
+ testClockWaitFor(new Clock.Virtual());
+ }
+
+ private void testClockWaitUntil(Clock c) throws InterruptedException {
+ long start = c.millis();
+ long delta = 100;
+ long until = start + delta;
+ c.waitUntil(until);
+
+ assertTrue(c.millis() - start >= delta);
+ }
+
+ private void testClockWaitFor(Clock c) throws InterruptedException {
+ long start = c.millis();
+ long delta = 100;
+ c.waitFor(delta);
+
+ assertTrue(c.millis() - start >= delta);
+
+ start = c.millis();
+ c.waitFor(Duration.ofMillis(100));
+
+ assertTrue(c.millis() - start >= delta);
+ }
+
+ @Test
+ public void testClockJavaTime() {
Clock c = Clock.SIMPLE;
long t1 = c.millis();
long t2 = c.getTime();
long t3 = c.millis();
+ Instant i4 = c.instant();
assertTrue(t1 <= t2);
assertTrue(t2 <= t3);
+ assertTrue(t3 <= i4.toEpochMilli());
java.time.Clock c2 = c.withZone(ZoneId.of("Z"));
assertEquals(c2.getZone(), c.getZone());
}
+ @Test
+ public void testNonTicking() {
+ NonTickingTestClock ntc = new NonTickingTestClock();
+ assertEquals(0, ntc.millis());
+ ntc.setTime(1000);
+ assertEquals(1000, ntc.millis());
+ ntc.setTime(500);
+ assertEquals(500, ntc.millis());
+ }
+
private void testClockDrift(Clock clock) throws InterruptedException {
long drift = clock.getTime() - System.currentTimeMillis();
diff --git
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/NonTickingTestClock.java
similarity index 69%
copy from
oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
copy to
oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/NonTickingTestClock.java
index b04ab9e46d..a5379be4df 100644
---
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/stats/package-info.java
+++
b/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/stats/NonTickingTestClock.java
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -14,7 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.2.0")
package org.apache.jackrabbit.oak.stats;
-import org.osgi.annotation.versioning.Version;
+/**
+ * Simple non-ticking clock that can be set (for use in test cases).
+ */
+public class NonTickingTestClock extends Clock {
+
+ long time = 0;
+
+ @Override
+ public long getTime() {
+ return this.time;
+ }
+
+ public void setTime(long time) {
+ this.time = time;
+ }
+}