This is an automated email from the ASF dual-hosted git repository.
cshannon pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/main by this push:
new 6dcf84ed00 Create a SteadyTime type in the Manager (#4494)
6dcf84ed00 is described below
commit 6dcf84ed00eca6129eee6a559beebd625dadf966
Author: Christopher L. Shannon <[email protected]>
AuthorDate: Fri May 10 09:55:34 2024 -0400
Create a SteadyTime type in the Manager (#4494)
* Create a SteadyTime type in the Manager
This replaces Manager.getSteadyTime() long value with a concrete type to
make it more apparent what time is being used. The serialization
and deserialization logic have been encapsulated as methods to make the
conversion consistent.
This closes #4482
---
.../apache/accumulo/core/util/time/SteadyTime.java | 84 +++++++++++++
.../accumulo/core/util/time/SteadyTimeTest.java | 56 +++++++++
server/manager/pom.xml | 5 +
.../java/org/apache/accumulo/manager/Manager.java | 9 +-
.../org/apache/accumulo/manager/ManagerTime.java | 120 ++++++++++++++++---
.../accumulo/manager/TabletGroupWatcher.java | 6 +-
.../apache/accumulo/manager/ManagerTimeTest.java | 130 +++++++++++++++++++++
7 files changed, 389 insertions(+), 21 deletions(-)
diff --git
a/core/src/main/java/org/apache/accumulo/core/util/time/SteadyTime.java
b/core/src/main/java/org/apache/accumulo/core/util/time/SteadyTime.java
new file mode 100644
index 0000000000..a94ae0ce55
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/util/time/SteadyTime.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.accumulo.core.util.time;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * SteadyTime represents an approximation of the total duration of time this
cluster has had a
+ * Manager. Because this represents an elapsed time it is guaranteed to not be
negative. SteadyTime
+ * is not expected to represent real world date times, its main use is for
computing deltas similar
+ * System.nanoTime but across JVM processes.
+ */
+public class SteadyTime implements Comparable<SteadyTime> {
+
+ private final Duration time;
+
+ private SteadyTime(Duration time) {
+ Preconditions.checkArgument(!time.isNegative(), "SteadyTime '%s' should
not be negative.",
+ time.toNanos());
+ this.time = time;
+ }
+
+ public long getMillis() {
+ return time.toMillis();
+ }
+
+ public long getNanos() {
+ return time.toNanos();
+ }
+
+ public Duration getDuration() {
+ return time;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SteadyTime that = (SteadyTime) o;
+ return Objects.equals(time, that.time);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(time);
+ }
+
+ @Override
+ public int compareTo(SteadyTime other) {
+ return time.compareTo(other.time);
+ }
+
+ public static SteadyTime from(long time, TimeUnit unit) {
+ return new SteadyTime(Duration.of(time, unit.toChronoUnit()));
+ }
+
+ public static SteadyTime from(Duration time) {
+ return new SteadyTime(time);
+ }
+}
diff --git
a/core/src/test/java/org/apache/accumulo/core/util/time/SteadyTimeTest.java
b/core/src/test/java/org/apache/accumulo/core/util/time/SteadyTimeTest.java
new file mode 100644
index 0000000000..016c771a8d
--- /dev/null
+++ b/core/src/test/java/org/apache/accumulo/core/util/time/SteadyTimeTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.accumulo.core.util.time;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+
+public class SteadyTimeTest {
+
+ @Test
+ public void testSteadyTime() {
+ long time = 20_000;
+ var steadyTime = SteadyTime.from(time, TimeUnit.NANOSECONDS);
+
+ assertEquals(time, steadyTime.getNanos());
+ assertEquals(TimeUnit.NANOSECONDS.toMillis(time), steadyTime.getMillis());
+ assertEquals(Duration.ofNanos(time), steadyTime.getDuration());
+
+ // Verify equals and compareTo work correctly for same
+ var steadyTime2 = SteadyTime.from(time, TimeUnit.NANOSECONDS);
+ assertEquals(steadyTime, steadyTime2);
+ assertEquals(0, steadyTime.compareTo(steadyTime2));
+
+ // Check equals/compareto different objects
+ var steadyTime3 = SteadyTime.from(time + 100, TimeUnit.NANOSECONDS);
+ assertNotEquals(steadyTime, steadyTime3);
+ assertTrue(steadyTime.compareTo(steadyTime3) < 1);
+
+ // Negatives are not allowed
+ assertThrows(IllegalArgumentException.class, () -> SteadyTime.from(-100,
TimeUnit.NANOSECONDS));
+ }
+
+}
diff --git a/server/manager/pom.xml b/server/manager/pom.xml
index 0511f60527..0213cf2fc9 100644
--- a/server/manager/pom.xml
+++ b/server/manager/pom.xml
@@ -128,5 +128,10 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
b/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
index 5c6cc2f436..ded6d62f83 100644
--- a/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
+++ b/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
@@ -114,6 +114,7 @@ import org.apache.accumulo.core.util.Retry;
import org.apache.accumulo.core.util.threads.ThreadPools;
import org.apache.accumulo.core.util.threads.Threads;
import org.apache.accumulo.core.util.time.NanoTime;
+import org.apache.accumulo.core.util.time.SteadyTime;
import org.apache.accumulo.manager.metrics.ManagerMetrics;
import org.apache.accumulo.manager.recovery.RecoveryManager;
import org.apache.accumulo.manager.state.TableCounts;
@@ -1726,11 +1727,11 @@ public class Manager extends AbstractServer
}
/**
- * Return how long (in milliseconds) there has been a manager overseeing
this cluster. This is an
- * approximately monotonic clock, which will be approximately consistent
between different
- * managers or different runs of the same manager.
+ * Return how long there has been a manager overseeing this cluster. This is
an approximately
+ * monotonic clock, which will be approximately consistent between different
managers or different
+ * runs of the same manager. SteadyTime supports both nanoseconds and
milliseconds.
*/
- public Long getSteadyTime() {
+ public SteadyTime getSteadyTime() {
return timeKeeper.getTime();
}
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/ManagerTime.java
b/server/manager/src/main/java/org/apache/accumulo/manager/ManagerTime.java
index 8bd842fc9d..fa7020a0ef 100644
--- a/server/manager/src/main/java/org/apache/accumulo/manager/ManagerTime.java
+++ b/server/manager/src/main/java/org/apache/accumulo/manager/ManagerTime.java
@@ -20,11 +20,12 @@ package org.apache.accumulo.manager;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.io.IOException;
-import java.util.concurrent.atomic.AtomicLong;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
@@ -32,9 +33,13 @@ import
org.apache.accumulo.core.fate.zookeeper.ZooReaderWriter;
import org.apache.accumulo.core.fate.zookeeper.ZooUtil.NodeExistsPolicy;
import org.apache.accumulo.core.util.threads.ThreadPools;
import org.apache.accumulo.core.util.threads.Threads;
+import org.apache.accumulo.core.util.time.SteadyTime;
+import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
+
/**
* Keep a persistent roughly monotone view of how long a manager has been
overseeing this cluster.
*/
@@ -47,9 +52,41 @@ public class ManagerTime {
/**
* Difference between time stored in ZooKeeper and System.nanoTime() when we
last read from
- * ZooKeeper.
+ * ZooKeeper. This offset may be negative or positive (depending on if the
current nanoTime of the
+ * system is negative or positive) and is represented as a Duration to make
computing future
+ * updates to the skewAmount and SteadyTime simpler.
+ * <p>
+ * Example where the skewAmount would be negative:
+ * <ul>
+ * <li>There's an existing persisted SteadyTime duration stored in Zookeeper
from the total
+ * previous manager runs of 1,000,000</li>
+ * <li>Manager starts up and reads the previous value and the gets the
current nano time which is
+ * 2,000,000</li>
+ * <li>The skew gets computed as the previous steady time duration minus the
current time, so that
+ * becomes: 1,000,000 - 2,000,000 = -1,000,000 resulting in the skew value
being negative 1
+ * million in this case</li>
+ * <li>When reading the current SteadyTime from the API, a new SteadyTime is
computed by adding
+ * the current nano time plus the skew. So let's say 100,000 ns have elapsed
since the start, so
+ * the current time is now 2,100,000. This results in:(-1,000,000) +
2,100,000 = 1,100,000. You
+ * end up with 1.1 million as a SteadyTime value that is the current elapsed
time of 100,000 for
+ * the current manager run plus the previous SteadyTime of 1 million that
was read on start.</li>
+ * </ul>
+ *
+ * Example where the skewAmount would be positive:
+ * <ul>
+ * <li>The current persisted value from previous runs is 1,000,000</li>
+ * <li>Manager starts up gets the current nano time which is -2,000,000</li>
+ * <li>The skew gets computed as: 1,000,000 - (-2,000,000) = 3,000,000
resulting in the skew value
+ * being positive 3 million in this case</li>
+ * <li>When reading the current SteadyTime from the API, a new SteadyTime is
computed by adding
+ * the current nano time plus the skew. So let's say 100,000 ns have elapsed
since the start, so
+ * the current time is now -1,900,000. This results in: (3,000,000) +
(-1,900,000) = 1,100,000.
+ * You end up with 1.1 million as a SteadyTime value that is the current
elapsed time of 100,000
+ * for the current manager run plus the previous SteadyTime of 1 million
that was read on
+ * start.</li>
+ * </ul>
*/
- private final AtomicLong skewAmount;
+ private final AtomicReference<Duration> skewAmount;
public ManagerTime(Manager manager, AccumuloConfiguration conf) throws
IOException {
this.zPath = manager.getZooKeeperRoot() + Constants.ZMANAGER_TICK;
@@ -58,24 +95,23 @@ public class ManagerTime {
try {
zk.putPersistentData(zPath, "0".getBytes(UTF_8), NodeExistsPolicy.SKIP);
- skewAmount =
- new AtomicLong(Long.parseLong(new String(zk.getData(zPath), UTF_8))
- System.nanoTime());
+ skewAmount = new AtomicReference<>(updateSkew(getZkTime()));
} catch (Exception ex) {
throw new IOException("Error updating manager time", ex);
}
ThreadPools.watchCriticalScheduledTask(manager.getContext().getScheduledExecutor()
- .scheduleWithFixedDelay(Threads.createNamedRunnable("Manager time
keeper", () -> run()), 0,
+ .scheduleWithFixedDelay(Threads.createNamedRunnable("Manager time
keeper", this::run), 0,
SECONDS.toMillis(10), MILLISECONDS));
}
/**
* How long has this cluster had a Manager?
*
- * @return Approximate total duration this cluster has had a Manager, in
milliseconds.
+ * @return Approximate total duration this cluster has had a Manager
*/
- public long getTime() {
- return NANOSECONDS.toMillis(System.nanoTime() + skewAmount.get());
+ public SteadyTime getTime() {
+ return fromSkew(skewAmount.get());
}
public void run() {
@@ -86,8 +122,7 @@ public class ManagerTime {
case INITIAL:
case STOP:
try {
- long zkTime = Long.parseLong(new String(zk.getData(zPath), UTF_8));
- skewAmount.set(zkTime - System.nanoTime());
+ skewAmount.set(updateSkew(getZkTime()));
} catch (Exception ex) {
if (log.isDebugEnabled()) {
log.debug("Failed to retrieve manager tick time", ex);
@@ -101,8 +136,7 @@ public class ManagerTime {
case UNLOAD_METADATA_TABLETS:
case UNLOAD_ROOT_TABLET:
try {
- zk.putPersistentData(zPath,
- Long.toString(System.nanoTime() +
skewAmount.get()).getBytes(UTF_8),
+ zk.putPersistentData(zPath, serialize(fromSkew(skewAmount.get())),
NodeExistsPolicy.OVERWRITE);
} catch (Exception ex) {
if (log.isDebugEnabled()) {
@@ -111,4 +145,62 @@ public class ManagerTime {
}
}
}
+
+ private SteadyTime getZkTime() throws InterruptedException, KeeperException {
+ return deserialize(zk.getData(zPath));
+ }
+
+ /**
+ * Creates a new skewAmount from an existing SteadyTime steadyTime -
System.nanoTime()
+ *
+ * @param steadyTime existing steadyTime
+ * @return Updated skew
+ */
+ @VisibleForTesting
+ static Duration updateSkew(SteadyTime steadyTime) {
+ return updateSkew(steadyTime, System.nanoTime());
+ }
+
+ /**
+ * Creates a new skewAmount from an existing SteadyTime by subtracting the
given time value
+ *
+ * @param steadyTime existing steadyTime
+ * @param time time to subtract to update skew
+ * @return Updated skew
+ */
+ @VisibleForTesting
+ static Duration updateSkew(SteadyTime steadyTime, long time) {
+ return Duration.ofNanos(steadyTime.getNanos() - time);
+ }
+
+ /**
+ * Create a new SteadyTime from a skewAmount using System.nanoTime() +
skewAmount
+ *
+ * @param skewAmount the skew amount to add
+ * @return A SteadyTime that has been skewed by the given skewAmount
+ */
+ @VisibleForTesting
+ static SteadyTime fromSkew(Duration skewAmount) {
+ return fromSkew(System.nanoTime(), skewAmount);
+ }
+
+ /**
+ * Create a new SteadyTime from a given time in ns and skewAmount using time
+ skewAmount
+ *
+ * @param time time to add the skew amount to
+ * @param skewAmount the skew amount to add
+ * @return A SteadyTime that has been skewed by the given skewAmount
+ */
+ @VisibleForTesting
+ static SteadyTime fromSkew(long time, Duration skewAmount) {
+ return SteadyTime.from(skewAmount.plusNanos(time));
+ }
+
+ static SteadyTime deserialize(byte[] steadyTime) {
+ return SteadyTime.from(Long.parseLong(new String(steadyTime, UTF_8)),
TimeUnit.NANOSECONDS);
+ }
+
+ static byte[] serialize(SteadyTime steadyTime) {
+ return Long.toString(steadyTime.getNanos()).getBytes(UTF_8);
+ }
}
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
b/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
index c3a77f2297..530bd950b1 100644
---
a/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
+++
b/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
@@ -355,7 +355,7 @@ abstract class TabletGroupWatcher extends
AccumuloDaemonThread {
Manager.log.trace("[{}] Requesting TabletServer {} unload
{} {}", store.name(),
location.getServerInstance(), tls.extent,
goal.howUnload());
client.unloadTablet(manager.managerLock, tls.extent,
goal.howUnload(),
- manager.getSteadyTime());
+ manager.getSteadyTime().getMillis());
unloaded++;
totalUnloaded++;
} catch (TException tException) {
@@ -454,7 +454,7 @@ abstract class TabletGroupWatcher extends
AccumuloDaemonThread {
private void hostSuspendedTablet(TabletLists tLists, TabletLocationState
tls, Location location,
TableConfiguration tableConf) {
- if (manager.getSteadyTime() - tls.suspend.suspensionTime
+ if (manager.getSteadyTime().getMillis() - tls.suspend.suspensionTime
< tableConf.getTimeInMillis(Property.TABLE_SUSPEND_DURATION)) {
// Tablet is suspended. See if its tablet server is back.
TServerInstance returnInstance = null;
@@ -1386,7 +1386,7 @@ abstract class TabletGroupWatcher extends
AccumuloDaemonThread {
deadTablets.subList(0, maxServersToShow));
Manager.log.debug("logs for dead servers: {}", deadLogs);
if (canSuspendTablets()) {
- store.suspend(deadTablets, deadLogs, manager.getSteadyTime());
+ store.suspend(deadTablets, deadLogs,
manager.getSteadyTime().getMillis());
} else {
store.unassign(deadTablets, deadLogs);
}
diff --git
a/server/manager/src/test/java/org/apache/accumulo/manager/ManagerTimeTest.java
b/server/manager/src/test/java/org/apache/accumulo/manager/ManagerTimeTest.java
new file mode 100644
index 0000000000..528eccca6d
--- /dev/null
+++
b/server/manager/src/test/java/org/apache/accumulo/manager/ManagerTimeTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.accumulo.manager;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.accumulo.core.util.time.SteadyTime;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class ManagerTimeTest {
+
+ @Test
+ public void testSteadyTime() {
+ long time = 20_000;
+ var steadyTime = SteadyTime.from(time, TimeUnit.NANOSECONDS);
+
+ // make sure calling serialize on instance matches static helper
+ byte[] serialized = ManagerTime.serialize(steadyTime);
+ assertArrayEquals(serialized, ManagerTime.serialize(steadyTime));
+
+ // Verify deserialization matches original object
+ var deserialized = ManagerTime.deserialize(serialized);
+ assertEquals(steadyTime, deserialized);
+ assertEquals(0, steadyTime.compareTo(deserialized));
+ }
+
+ @ParameterizedTest
+ // Test with both a 0 and positive previous value. This simulates the value
+ // read out of zookeeper for the time and should never be negative as it is
+ // based on elapsed time from previous manager runs
+ @ValueSource(longs = {0, 50_000})
+ public void testSteadyTimeFromSkew(long previousTime) throws
InterruptedException {
+ List<Long> times = List.of(-100_000L, -100L, 0L, 20_000L,
System.nanoTime());
+
+ for (Long time : times) {
+ // ManagerTime builds the skew amount by subtracting the current nanotime
+ // from the previous persisted time in ZK. The skew can be negative or
positive because
+ // it will depend on if the current nanotime is negative or positive as
+ // nanotime is allowed to be negative
+ var skewAmount =
+ ManagerTime.updateSkew(SteadyTime.from(previousTime,
TimeUnit.NANOSECONDS), time);
+
+ // Build a SteadyTime using the skewAmount
+ // SteadyTime should never be negative
+ var original = ManagerTime.fromSkew(time, skewAmount);
+
+ // Simulate a future time and create another SteadyTime from the skew
which should
+ // now be after the original
+ time = time + 10000;
+ var futureSkew = ManagerTime.fromSkew(time, skewAmount);
+
+ // future should be after the original
+ assertTrue(futureSkew.compareTo(original) > 0);
+ }
+ }
+
+ @ParameterizedTest
+ // Test with both a 0 and positive previous value. This simulates the value
+ // read out of zookeeper for the time and should never be negative as it is
+ // based on elapsed time from previous manager runs
+ @ValueSource(longs = {0, 50_000})
+ public void testSteadyTimeFromSkewCurrent(long previousTime) throws
InterruptedException {
+ // Also test fromSkew(skewAmount) method which only uses System.nanoTime()
+ var skewAmount = ManagerTime.updateSkew(SteadyTime.from(previousTime,
TimeUnit.NANOSECONDS));
+
+ // Build a SteadyTime using the skewAmount and current time
+ var original = ManagerTime.fromSkew(skewAmount);
+
+ // sleep a bit so time elapses
+ Thread.sleep(10);
+ var futureSkew = ManagerTime.fromSkew(skewAmount);
+
+ // future should be after the original
+ assertTrue(futureSkew.compareTo(original) > 0);
+ }
+
+ @ParameterizedTest
+ // Test with both a 0 and positive previous value. This simulates the value
+ // read out of zookeeper for the time and should never be negative as it is
+ // based on elapsed time from previous manager runs
+ @ValueSource(longs = {0, 50_000})
+ public void testSteadyTimeUpdateSkew(long previousTime) throws
InterruptedException {
+
+ var steadyTime = SteadyTime.from(previousTime, TimeUnit.NANOSECONDS);
+ List<Long> times = List.of(-100_000L, -100L, 0L, 20_000L,
System.nanoTime());
+
+ // test updateSkew with various times and previous times
+ for (Long time : times) {
+ var expected = steadyTime.getNanos() - time;
+
+ // test that updateSkew computes the update as current steadyTime - time
+ var skewAmount = ManagerTime.updateSkew(steadyTime, time);
+ assertEquals(expected, skewAmount.toNanos());
+ }
+
+ // test updateSkew with current system time
+ var skew = ManagerTime.updateSkew(steadyTime);
+ // sleep a bit so time elapses
+ Thread.sleep(10);
+ var updatedSkew = ManagerTime.updateSkew(steadyTime);
+ // Updating the skew time subtracts the current nanotime from
+ // the previous value so the updated value should be less than
+ // a previously created SteadyTime based on the same skew
+ assertTrue(skew.toNanos() - updatedSkew.toNanos() > 0);
+ assertTrue(skew.compareTo(updatedSkew) > 0);
+ }
+}