From 807d1858e92c18e30fb668fd4ce1ad7a164890e6 Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Mon, 31 Jul 2023 22:35:56 -0600
Subject: [PATCH 01/11] Add `Timer` and use it in `Timeout`
JAVA-5076
---
.../internal/connection/ConcurrentPool.java | 8 +-
.../internal/connection/ConnectionPool.java | 4 +-
.../connection/DefaultConnectionPool.java | 9 +-
.../mongodb/internal/{ => time}/Timeout.java | 128 ++++--------
.../main/com/mongodb/internal/time/Timer.java | 182 ++++++++++++++++++
.../mongodb/internal/time/package-info.java | 23 +++
.../connection/DefaultConnectionPoolTest.java | 5 +-
.../internal/{ => time}/TimeoutTest.java | 55 +++---
.../com/mongodb/internal/time/TimerTest.java | 76 ++++++++
...erverDiscoveryAndMonitoringProseTests.java | 5 +-
10 files changed, 368 insertions(+), 127 deletions(-)
rename driver-core/src/main/com/mongodb/internal/{ => time}/Timeout.java (55%)
create mode 100644 driver-core/src/main/com/mongodb/internal/time/Timer.java
create mode 100644 driver-core/src/main/com/mongodb/internal/time/package-info.java
rename driver-core/src/test/unit/com/mongodb/internal/{ => time}/TimeoutTest.java (77%)
create mode 100644 driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java
diff --git a/driver-core/src/main/com/mongodb/internal/connection/ConcurrentPool.java b/driver-core/src/main/com/mongodb/internal/connection/ConcurrentPool.java
index 0158c2d9637..30be9d69b17 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/ConcurrentPool.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/ConcurrentPool.java
@@ -23,6 +23,8 @@
import com.mongodb.MongoTimeoutException;
import com.mongodb.annotations.ThreadSafe;
import com.mongodb.internal.VisibleForTesting;
+import com.mongodb.internal.time.Timeout;
+import com.mongodb.internal.time.Timer;
import com.mongodb.lang.Nullable;
import java.util.Deque;
@@ -142,7 +144,7 @@ public T get() {
* Gets an object from the pool. Blocks until an object is available, or the specified {@code timeout} expires,
* or the pool is {@linkplain #close() closed}/{@linkplain #pause(Supplier) paused}.
*
- * @param timeout See {@link com.mongodb.internal.Timeout#startNow(long, TimeUnit)}.
+ * @param timeout See {@link Timeout#started(long, TimeUnit, Timer)}.
* @param timeUnit the time unit of the timeout
* @return An object from the pool, or null if can't get one in the given waitTime
* @throws MongoTimeoutException if the timeout has been exceeded
@@ -226,7 +228,7 @@ private T createNewAndReleasePermitIfFailure() {
}
/**
- * @param timeout See {@link com.mongodb.internal.Timeout#startNow(long, TimeUnit)}.
+ * @param timeout See {@link Timeout#started(long, TimeUnit, Timer)}.
*/
@VisibleForTesting(otherwise = PRIVATE)
boolean acquirePermit(final long timeout, final TimeUnit timeUnit) {
@@ -386,7 +388,7 @@ boolean acquirePermitImmediateUnfair() {
* This method also emulates the eager {@link InterruptedException} behavior of
* {@link java.util.concurrent.Semaphore#tryAcquire(long, TimeUnit)}.
*
- * @param timeout See {@link com.mongodb.internal.Timeout#startNow(long, TimeUnit)}.
+ * @param timeout See {@link Timeout#started(long, TimeUnit, Timer)}.
*/
boolean acquirePermit(final long timeout, final TimeUnit unit) throws MongoInterruptedException {
long remainingNanos = unit.toNanos(timeout);
diff --git a/driver-core/src/main/com/mongodb/internal/connection/ConnectionPool.java b/driver-core/src/main/com/mongodb/internal/connection/ConnectionPool.java
index da2b0dcc1a0..ad7ed612275 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/ConnectionPool.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/ConnectionPool.java
@@ -20,6 +20,8 @@
import com.mongodb.annotations.ThreadSafe;
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.internal.async.SingleResultCallback;
+import com.mongodb.internal.time.Timeout;
+import com.mongodb.internal.time.Timer;
import org.bson.types.ObjectId;
import com.mongodb.lang.Nullable;
@@ -38,7 +40,7 @@ interface ConnectionPool extends Closeable {
/**
* @param operationContext operation context
- * @param timeout See {@link com.mongodb.internal.Timeout#startNow(long, TimeUnit)}.
+ * @param timeout See {@link Timeout#started(long, TimeUnit, Timer)}.
* @throws MongoConnectionPoolClearedException If detects that the pool is {@linkplain #invalidate(Throwable) paused}.
*/
InternalConnection get(OperationContext operationContext, long timeout, TimeUnit timeUnit) throws MongoConnectionPoolClearedException;
diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java
index b20b1e7c57e..d9461046bd3 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java
@@ -43,7 +43,7 @@
import com.mongodb.event.ConnectionPoolListener;
import com.mongodb.event.ConnectionPoolReadyEvent;
import com.mongodb.event.ConnectionReadyEvent;
-import com.mongodb.internal.Timeout;
+import com.mongodb.internal.time.Timeout;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.connection.SdamServerDescriptionManager.SdamIssue;
@@ -55,6 +55,7 @@
import com.mongodb.internal.logging.StructuredLogger;
import com.mongodb.internal.session.SessionContext;
import com.mongodb.internal.thread.DaemonThreadFactory;
+import com.mongodb.internal.time.Timer;
import com.mongodb.lang.NonNull;
import com.mongodb.lang.Nullable;
import org.bson.ByteBuf;
@@ -190,7 +191,7 @@ public InternalConnection get(final OperationContext operationContext) {
@Override
public InternalConnection get(final OperationContext operationContext, final long timeoutValue, final TimeUnit timeUnit) {
connectionCheckoutStarted(operationContext);
- Timeout timeout = Timeout.startNow(timeoutValue, timeUnit);
+ Timeout timeout = Timeout.started(timeoutValue, timeUnit, Timer.start());
try {
stateAndGeneration.throwIfClosedOrPaused();
PooledConnection connection = getPooledConnection(timeout);
@@ -208,7 +209,7 @@ public InternalConnection get(final OperationContext operationContext, final lon
@Override
public void getAsync(final OperationContext operationContext, final SingleResultCallback callback) {
connectionCheckoutStarted(operationContext);
- Timeout timeout = Timeout.startNow(settings.getMaxWaitTime(NANOSECONDS));
+ Timeout timeout = Timeout.started(settings.getMaxWaitTime(NANOSECONDS), Timer.start());
SingleResultCallback eventSendingCallback = (connection, failure) -> {
SingleResultCallback errHandlingCallback = errorHandlingCallback(callback, LOGGER);
if (failure == null) {
@@ -1123,7 +1124,7 @@ void signalClosedOrPaused() {
}
/**
- * @param timeoutNanos See {@link Timeout#startNow(long)}.
+ * @param timeoutNanos See {@link Timeout#started(long, Timer)}.
* @return The remaining duration as per {@link Timeout#remainingOrInfinite(TimeUnit)} if waiting ended early either
* spuriously or because of receiving a signal.
*/
diff --git a/driver-core/src/main/com/mongodb/internal/Timeout.java b/driver-core/src/main/com/mongodb/internal/time/Timeout.java
similarity index 55%
rename from driver-core/src/main/com/mongodb/internal/Timeout.java
rename to driver-core/src/main/com/mongodb/internal/time/Timeout.java
index f36eedd1d64..9bcbc4453aa 100644
--- a/driver-core/src/main/com/mongodb/internal/Timeout.java
+++ b/driver-core/src/main/com/mongodb/internal/time/Timeout.java
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.mongodb.internal;
+package com.mongodb.internal.time;
import com.mongodb.annotations.Immutable;
+import com.mongodb.internal.VisibleForTesting;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static com.mongodb.assertions.Assertions.assertFalse;
-import static com.mongodb.assertions.Assertions.assertNotNull;
import static com.mongodb.assertions.Assertions.assertTrue;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -29,125 +29,81 @@
/**
* A value-based class
- * useful for tracking timeouts.
- *
- * This class is not part of the public API and may be removed or changed at any time
+ * for tracking timeouts.
+ *
+ * This class is not part of the public API and may be removed or changed at any time.
*/
@Immutable
public final class Timeout {
- private static final Timeout INFINITE = new Timeout(-1, 0);
- private static final Timeout IMMEDIATE = new Timeout(0, 0);
+ private static final Timeout INFINITE = new Timeout(-1, Timer.useless());
+ private static final Timeout IMMEDIATE = new Timeout(0, Timer.useless());
private final long durationNanos;
- private final long startNanos;
+ private final Timer timer;
- private Timeout(final long durationNanos, final long startNanos) {
+ private Timeout(final long durationNanos, final Timer timer) {
this.durationNanos = durationNanos;
- this.startNanos = startNanos;
+ this.timer = timer;
}
/**
- * Converts the specified {@code duration} from {@code unit}s to {@link TimeUnit#NANOSECONDS} via {@link TimeUnit#toNanos(long)}
- * and then acts identically to {@link #startNow(long)}.
+ * Converts the specified {@code duration} from {@code unit}s to {@link TimeUnit#NANOSECONDS}
+ * as specified by {@link TimeUnit#toNanos(long)} and then acts identically to {@link #started(long, Timer)}.
*
* Note that the contract of this method is also used in some places to specify the behavior of methods that accept
* {@code (long timeout, TimeUnit unit)}, e.g., {@link com.mongodb.internal.connection.ConcurrentPool#get(long, TimeUnit)},
- * so it cannot be changed without updating those methods.
- * @see #startNow(long)
+ * so it cannot be changed without updating those methods.
+ * @see #started(long, Timer)
*/
- public static Timeout startNow(final long duration, final TimeUnit unit) {
- assertNotNull(unit);
- return startNow(unit.toNanos(duration));
+ public static Timeout started(final long duration, final TimeUnit unit, final Timer timer) {
+ return started(unit.toNanos(duration), timer);
}
/**
* Returns an {@linkplain #isInfinite() infinite} timeout if {@code durationNanos} is either negative
* or is equal to {@link Long#MAX_VALUE},
* an {@linkplain #isImmediate() immediate} timeout if {@code durationNanos} is 0,
- * otherwise an object that represents the specified {@code durationNanos}.
+ * otherwise a timeout of {@code durationNanos}.
*
* Note that the contract of this method is also used in some places to specify the behavior of methods that accept
* {@code (long timeout, TimeUnit unit)}, e.g., {@link com.mongodb.internal.connection.ConcurrentPool#get(long, TimeUnit)},
- * so it cannot be changed without updating those methods.
+ * so it cannot be changed without updating those methods.
*/
- public static Timeout startNow(final long durationNanos) {
+ public static Timeout started(final long durationNanos, final Timer timer) {
if (durationNanos < 0 || durationNanos == Long.MAX_VALUE) {
return infinite();
} else if (durationNanos == 0) {
return immediate();
} else {
- return new Timeout(durationNanos, System.nanoTime());
+ return new Timeout(durationNanos, timer);
}
}
/**
- * @see #startNow(long)
+ * @see #started(long, Timer)
*/
public static Timeout infinite() {
return INFINITE;
}
/**
- * @see #startNow(long)
+ * @see #started(long, Timer)
*/
public static Timeout immediate() {
return IMMEDIATE;
}
- /**
- * Must not be called on {@linkplain #isInfinite() infinite} or {@linkplain #isImmediate() immediate} timeouts.
- *
- * Returns {@code currentNanos} - {@link #startNanos}:
- *
- * -
- * A negative value means either of the following
- *
- * - the clock from which {@code currentNanos} was read jumped backwards,
- * in which case the behaviour of this class is undefined;
- * - (n * 263 - 1; (n + 1) * 263)(*) nanoseconds has elapsed,
- * in which case the timeout has expired.
- *
- *
- * -
- * 0 means either of the following
- *
- * - 0 nanoseconds has elapsed;
- * - (n + 1) * 263(*) nanoseconds has elapsed,
- * in which case the timeout has expired.
- *
- * Since it is impossible to differentiate the former from the latter, and the former is much more likely to happen in practice,
- * this class interprets 0 value as 0 elapsed nanoseconds.
- *
- * -
- * A positive value means either of the following
- *
- * - this exact number of nanoseconds has elapsed;
- * - ((n + 1) * 263; (n + 2) * 263 - 1](*) nanoseconds has elapsed,
- * in which case the timeout has expired.
- *
- * Since it is impossible to differentiate the former from the latter, and the former is much more likely to happen in practice,
- * this class interprets a positive value as the exact number of elapsed nanoseconds.
- *
- *
- *
- * (*) n is positive and odd.
- */
- private long elapsedNanos(final long currentNanos) {
- assertFalse(isInfinite() || isImmediate());
- return currentNanos - startNanos;
- }
-
/**
* Returns 0 or a positive value.
* 0 means that the timeout has expired.
- *
- * Must not be called on {@linkplain #isInfinite() infinite} timeouts.
+ *
+ * @throws AssertionError If the timeout is {@linkplain #isInfinite() infinite} or {@linkplain #isImmediate() immediate},
+ * because such timeouts use {@link Timer#useless()}.
*/
@VisibleForTesting(otherwise = PRIVATE)
- long remainingNanos(final long currentNanos) {
+ long nonNegativeRemainingNanos(final long currentNanos) {
assertFalse(isInfinite() || isImmediate());
- long elapsedNanos = elapsedNanos(currentNanos);
- return elapsedNanos < 0 ? 0 : Math.max(0, durationNanos - elapsedNanos);
+ return Math.max(0, durationNanos - timer.elapsedNanos(currentNanos));
}
/**
@@ -155,22 +111,21 @@ long remainingNanos(final long currentNanos) {
* Use {@link #expired(long)} to check if the returned value signifies that a timeout is expired.
*
* @param unit If not {@link TimeUnit#NANOSECONDS}, then coarsening conversion is done that may result in returning a value
- * that represents a longer time duration than is actually remaining (this is done to prevent treating a timeout as
- * {@linkplain #expired(long) expired} when it is not). Consequently, one should specify {@code unit} as small as
- * practically possible. Such rounding up happens if and only if the remaining time cannot be
- * represented exactly as an integral number of the {@code unit}s specified. It may result in
- * {@link #expired()} returning {@code true} and after that (in the happens-before order)
- * {@link #expired(long) expired}{@code (}{@link #remaining(TimeUnit) remaining(...)}{@code )}
- * returning {@code false}. If such a discrepancy is observed,
- * the result of the {@link #expired()} method should be preferred.
+ * that represents a longer time duration than is actually remaining (this is done to prevent treating a timeout as
+ * {@linkplain #expired(long) expired} when it is not). Consequently, one should specify {@code unit} as small as
+ * practically possible. Such rounding up happens if and only if the remaining time cannot be
+ * represented exactly as an integral number of the {@code unit}s specified. It may result in
+ * {@link #expired()} returning {@code true} and after that (in the happens-before order)
+ * {@link #expired(long) expired}{@code (}{@link #remaining(TimeUnit) remaining(...)}{@code )}
+ * returning {@code false}. If such a discrepancy is observed,
+ * the result of the {@link #expired()} method should be preferred.
*
* @throws AssertionError If the timeout is {@linkplain #isInfinite() infinite}.
* @see #remainingOrInfinite(TimeUnit)
*/
public long remaining(final TimeUnit unit) {
- assertNotNull(unit);
assertFalse(isInfinite());
- return isImmediate() ? 0 : convertRoundUp(remainingNanos(System.nanoTime()), unit);
+ return isImmediate() ? 0 : convertRoundUp(nonNegativeRemainingNanos(System.nanoTime()), unit);
}
/**
@@ -181,7 +136,6 @@ public long remaining(final TimeUnit unit) {
* @see #remaining(TimeUnit)
*/
public long remainingOrInfinite(final TimeUnit unit) {
- assertNotNull(unit);
return isInfinite() ? -1 : remaining(unit);
}
@@ -226,12 +180,12 @@ public boolean equals(final Object o) {
return false;
}
Timeout other = (Timeout) o;
- return durationNanos == other.durationNanos && startNanos == other.startNanos;
+ return durationNanos == other.durationNanos && timer == other.timer;
}
@Override
public int hashCode() {
- return Objects.hash(durationNanos, startNanos);
+ return Objects.hash(durationNanos, timer);
}
/**
@@ -243,7 +197,7 @@ public int hashCode() {
public String toString() {
return "Timeout{"
+ "durationNanos=" + durationNanos
- + ", startNanos=" + startNanos
+ + ", timer=" + timer
+ '}';
}
@@ -268,8 +222,8 @@ long durationNanos() {
}
@VisibleForTesting(otherwise = PRIVATE)
- long startNanos() {
- return startNanos;
+ Timer timer() {
+ return timer;
}
@VisibleForTesting(otherwise = PRIVATE)
diff --git a/driver-core/src/main/com/mongodb/internal/time/Timer.java b/driver-core/src/main/com/mongodb/internal/time/Timer.java
new file mode 100644
index 00000000000..881574ad46d
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/time/Timer.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * Licensed 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
+ *
+ * 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,
+ * 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 com.mongodb.internal.time;
+
+import com.mongodb.annotations.Immutable;
+import com.mongodb.annotations.NotThreadSafe;
+import com.mongodb.internal.VisibleForTesting;
+
+import java.util.concurrent.TimeUnit;
+
+import static com.mongodb.assertions.Assertions.assertTrue;
+import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+/**
+ * A value-based class
+ * for tracking elapsed time. The maximum duration this class can measure reliably is {@link Long#MAX_VALUE} nanoseconds,
+ * which is more than 292 years. The class must not be used to measure longer durations.
+ *
+ * This class is not part of the public API and may be removed or changed at any time.
+ */
+@Immutable
+public final class Timer {
+ private static final Timer USELESS = new Timer(0);
+
+ private final long startNanos;
+
+ private Timer(final long startNanos) {
+ this.startNanos = startNanos;
+ }
+
+ /**
+ * Returns a newly started {@link Timer}.
+ */
+ public static Timer start() {
+ return start(System.nanoTime());
+ }
+
+ /**
+ * Returns a {@link Timer} started at an unspecified instant.
+ */
+ public static Timer useless() {
+ return USELESS;
+ }
+
+ /**
+ * The duration in {@code unit}s measured by this {@link Timer}.
+ *
+ * @param unit {@link TimeUnit#convert(long, TimeUnit)} specifies how the conversion from nanoseconds to {@code timeUnit} is done.
+ */
+ public long elapsed(final TimeUnit unit) {
+ return unit.convert(elapsedNanos(System.nanoTime()), NANOSECONDS);
+ }
+
+ /**
+ * Creates a new {@link OneOff}.
+ */
+ public OneOff oneOff() {
+ return new OneOff(this);
+ }
+
+ /**
+ * Returns {@code currentNanos} - {@link #startNanos}:
+ *
+ * -
+ * A negative value means either of the following
+ *
+ * - the clock from which {@code currentNanos} was read jumped backwards,
+ * in which case the behaviour of this class is undefined;
+ * - (n * 263 - 1; (n + 1) * 263)(*) nanoseconds has elapsed.
+ *
+ *
+ * -
+ * 0 means either of the following
+ *
+ * - 0 nanoseconds has elapsed;
+ * - (n + 1) * 263(*) nanoseconds has elapsed.
+ *
+ * This class interprets 0 value as 0 elapsed nanoseconds.
+ *
+ * -
+ * A positive value means either of the following
+ *
+ * - this number of nanoseconds has elapsed;
+ * - ((n + 1) * 263; (n + 2) * 263 - 1](*) nanoseconds has elapsed.
+ *
+ * This class interprets a positive value as the number of elapsed nanoseconds.
+ *
+ *
+ *
+ * (*) n is positive and odd.
+ */
+ long elapsedNanos(final long currentNanos) {
+ long elapsedNanos = currentNanos - startNanos;
+ assertTrue(elapsedNanos >= 0);
+ return elapsedNanos;
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ static Timer start(final long startNanos) {
+ return new Timer(startNanos);
+ }
+
+ @VisibleForTesting(otherwise = PRIVATE)
+ long startNanos() {
+ return startNanos;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final Timer timer = (Timer) o;
+ return startNanos == timer.startNanos;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(startNanos);
+ }
+
+ @Override
+ public String toString() {
+ return "Timer{"
+ + "startNanos=" + startNanos
+ + '}';
+ }
+
+ /**
+ * A timer only capable for a one-off measurement.
+ *
+ * This class is not part of the public API and may be removed or changed at any time.
+ */
+ @NotThreadSafe
+ public static final class OneOff {
+ private static final long UNKNOWN = -1;
+
+ private final Timer timer;
+ private long value;
+
+ OneOff(final Timer timer) {
+ this.timer = timer;
+ value = UNKNOWN;
+ }
+
+ /**
+ * Does a one-off {@linkplain Timer#elapsed(TimeUnit) measurement}.
+ * The result is memoized and is returned by all {@link #memoizeAndGet(TimeUnit)} operations.
+ */
+ public long memoizeAndGet(final TimeUnit unit) {
+ if (value == UNKNOWN) {
+ value = timer.elapsed(unit);
+ }
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "OneOff{"
+ + "timer=" + timer
+ + ", value=" + value
+ + '}';
+ }
+ }
+}
diff --git a/driver-core/src/main/com/mongodb/internal/time/package-info.java b/driver-core/src/main/com/mongodb/internal/time/package-info.java
new file mode 100644
index 00000000000..3b3ee457517
--- /dev/null
+++ b/driver-core/src/main/com/mongodb/internal/time/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * Licensed 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
+ *
+ * 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,
+ * 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.
+ */
+
+/**
+ * This package contains program elements for working with time.
+ */
+@NonNullApi
+package com.mongodb.internal.time;
+
+import com.mongodb.lang.NonNullApi;
diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
index 91a08f63b22..432d8379d17 100644
--- a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
+++ b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
@@ -26,11 +26,12 @@
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.connection.ServerId;
import com.mongodb.event.ConnectionCreatedEvent;
-import com.mongodb.internal.Timeout;
+import com.mongodb.internal.time.Timeout;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.inject.EmptyProvider;
import com.mongodb.internal.inject.OptionalProvider;
import com.mongodb.internal.inject.SameObjectProvider;
+import com.mongodb.internal.time.Timer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
@@ -522,7 +523,7 @@ private static void useConcurrently(final DefaultConnectionPool pool, final int
}
};
Collection> tasks = new ArrayList<>();
- Timeout duration = Timeout.startNow(durationNanos);
+ Timeout duration = Timeout.started(durationNanos, Timer.start());
for (int i = 0; i < concurrentUsersCount; i++) {
if ((checkoutSync && checkoutAsync) ? i % 2 == 0 : checkoutSync) {//check out synchronously and check in
tasks.add(executor.submit(() -> {
diff --git a/driver-core/src/test/unit/com/mongodb/internal/TimeoutTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
similarity index 77%
rename from driver-core/src/test/unit/com/mongodb/internal/TimeoutTest.java
rename to driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
index ad96096a0dc..b0d8c63fec4 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/TimeoutTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.mongodb.internal;
+package com.mongodb.internal.time;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -27,10 +27,10 @@
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
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 static org.junit.jupiter.api.Assertions.assertFalse;
final class TimeoutTest {
@Test
@@ -38,7 +38,7 @@ void isInfinite() {
assertAll(
() -> assertTrue(Timeout.infinite().isInfinite()),
() -> assertFalse(Timeout.immediate().isInfinite()),
- () -> assertFalse(Timeout.startNow(1).isInfinite()));
+ () -> assertFalse(Timeout.started(1, Timer.start()).isInfinite()));
}
@Test
@@ -46,29 +46,31 @@ void isImmediate() {
assertAll(
() -> assertTrue(Timeout.immediate().isImmediate()),
() -> assertFalse(Timeout.infinite().isImmediate()),
- () -> assertFalse(Timeout.startNow(1).isImmediate()));
+ () -> assertFalse(Timeout.started(1, Timer.start()).isImmediate()));
}
@Test
- void startNow() {
+ void started() {
+ Timer timer = Timer.start();
assertAll(
- () -> assertEquals(Timeout.infinite(), Timeout.startNow(-1)),
- () -> assertEquals(Timeout.immediate(), Timeout.startNow(0)),
- () -> assertNotEquals(Timeout.infinite(), Timeout.startNow(1)),
- () -> assertNotEquals(Timeout.immediate(), Timeout.startNow(1)),
- () -> assertNotEquals(Timeout.infinite(), Timeout.startNow(Long.MAX_VALUE - 1)),
- () -> assertEquals(Timeout.infinite(), Timeout.startNow(Long.MAX_VALUE)));
+ () -> assertEquals(Timeout.infinite(), Timeout.started(-1, timer)),
+ () -> assertEquals(Timeout.immediate(), Timeout.started(0, timer)),
+ () -> assertNotEquals(Timeout.infinite(), Timeout.started(1, timer)),
+ () -> assertNotEquals(Timeout.immediate(), Timeout.started(1, timer)),
+ () -> assertNotEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE - 1, timer)),
+ () -> assertEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE, timer)));
}
@ParameterizedTest
@MethodSource("durationArguments")
- void startNowConvertsUnits(final long duration, final TimeUnit unit) {
+ void startedConvertsUnits(final long duration, final TimeUnit unit) {
+ Timer timer = Timer.start();
if (duration < 0) {
- assertTrue(Timeout.startNow(duration, unit).isInfinite());
+ assertTrue(Timeout.started(duration, unit, timer).isInfinite());
} else if (duration == 0) {
- assertTrue(Timeout.startNow(duration, unit).isImmediate());
+ assertTrue(Timeout.started(duration, unit, timer).isImmediate());
} else {
- assertEquals(unit.toNanos(duration), Timeout.startNow(duration, unit).durationNanos());
+ assertEquals(unit.toNanos(duration), Timeout.started(duration, unit, timer).durationNanos());
}
}
@@ -92,16 +94,12 @@ void remainingNanosTrivialCases() {
@ParameterizedTest
@ValueSource(longs = {1, 7, Long.MAX_VALUE / 2, Long.MAX_VALUE - 1})
void remainingNanos(final long durationNanos) {
- Timeout timeout = Timeout.startNow(durationNanos);
- long startNanos = timeout.startNanos();
- assertEquals(durationNanos, timeout.remainingNanos(startNanos));
- assertEquals(Math.max(0, durationNanos - 1), timeout.remainingNanos(startNanos + 1));
- assertEquals(0, timeout.remainingNanos(startNanos + durationNanos));
- assertEquals(0, timeout.remainingNanos(startNanos + durationNanos + 1));
- assertEquals(0, timeout.remainingNanos(startNanos + Long.MAX_VALUE));
- assertEquals(0, timeout.remainingNanos(startNanos + Long.MAX_VALUE + 1));
- assertEquals(0, timeout.remainingNanos(startNanos + Long.MAX_VALUE + Long.MAX_VALUE));
-
+ Timeout timeout = Timeout.started(durationNanos, Timer.start());
+ long startNanos = timeout.timer().startNanos();
+ assertEquals(durationNanos, timeout.nonNegativeRemainingNanos(startNanos));
+ assertEquals(Math.max(0, durationNanos - 1), timeout.nonNegativeRemainingNanos(startNanos + 1));
+ assertEquals(0, timeout.nonNegativeRemainingNanos(startNanos + durationNanos));
+ assertEquals(0, timeout.nonNegativeRemainingNanos(startNanos + durationNanos + 1));
}
@Test
@@ -132,10 +130,11 @@ void convertRoundUp() {
@ValueSource(longs = {1, 7, 10, 100, 1000})
void usesRealClock(final long durationNanos) {
long startNanosLowerBound = System.nanoTime();
- Timeout timeout = Timeout.startNow(durationNanos);
+ Timeout timeout = Timeout.started(durationNanos, Timer.start());
long startNanosUpperBound = System.nanoTime();
- assertTrue(timeout.startNanos() - startNanosLowerBound >= 0, "started too early");
- assertTrue(timeout.startNanos() - startNanosUpperBound <= 0, "started too late");
+ long startNanos = timeout.timer().startNanos();
+ assertTrue(startNanos - startNanosLowerBound >= 0, "started too early");
+ assertTrue(startNanos - startNanosUpperBound <= 0, "started too late");
while (!timeout.expired()) {
long remainingNanosUpperBound = Math.max(0, durationNanos - (System.nanoTime() - startNanosUpperBound));
long remainingNanos = timeout.remaining(NANOSECONDS);
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java
new file mode 100644
index 00000000000..a07779ca4f7
--- /dev/null
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * Licensed 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
+ *
+ * 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,
+ * 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 com.mongodb.internal.time;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+final class TimerTest {
+ @Test
+ void useless() {
+ assertAll(
+ () -> assertDoesNotThrow(Timer::useless),
+ () -> assertDoesNotThrow(() -> Timer.useless().oneOff().memoizeAndGet(NANOSECONDS)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(longs = {Long.MIN_VALUE, Long.MIN_VALUE / 2, -1, 0, 1, Long.MAX_VALUE / 2, Long.MAX_VALUE})
+ void start(final long startNanos) {
+ assertEquals(startNanos, Timer.start(startNanos).startNanos());
+ }
+
+ @ParameterizedTest
+ @ValueSource(longs = {0, 1, 7, Long.MAX_VALUE / 2, Long.MAX_VALUE - 1})
+ void elapsedNanos(final long currentNanos) {
+ assertEquals(currentNanos, Timer.start(0).elapsedNanos(currentNanos));
+ }
+
+ @ParameterizedTest
+ @ValueSource(longs = {1, 7, 10, 100, 1000})
+ void usesRealClock(final long sleepDurationMillis) throws InterruptedException {
+ long startNanosLowerBound = System.nanoTime();
+ Timer timer = Timer.start();
+ long startNanosUpperBound = System.nanoTime();
+ long startNanos = timer.startNanos();
+ assertTrue(startNanos - startNanosLowerBound >= 0, "started too early");
+ assertTrue(startNanos - startNanosUpperBound <= 0, "started too late");
+ Thread.sleep(sleepDurationMillis);
+ long elapsedNanosLowerBound = System.nanoTime() - startNanosUpperBound;
+ long elapsedNanos = timer.elapsed(NANOSECONDS);
+ long elapsedNanosUpperBound = System.nanoTime() - startNanosLowerBound;
+ assertTrue(elapsedNanos >= elapsedNanosLowerBound, "elapsed nanos is too low");
+ assertTrue(elapsedNanos <= elapsedNanosUpperBound, "elapsed nanos is too high");
+ }
+
+ @Test
+ void oneOff() throws InterruptedException {
+ Timer.OneOff oneOffTimer = Timer.start().oneOff();
+ Thread.sleep(7);
+ long expectedElapsedNanos = oneOffTimer.memoizeAndGet(NANOSECONDS);
+ Thread.sleep(11);
+ assertEquals(expectedElapsedNanos, oneOffTimer.memoizeAndGet(NANOSECONDS));
+ }
+
+ private TimerTest() {
+ }
+}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
index 0915486dc41..757ca999fd9 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
@@ -27,9 +27,10 @@
import com.mongodb.event.ServerHeartbeatSucceededEvent;
import com.mongodb.event.ServerListener;
import com.mongodb.event.ServerMonitorListener;
-import com.mongodb.internal.Timeout;
+import com.mongodb.internal.time.Timeout;
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
+import com.mongodb.internal.time.Timer;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
import org.bson.BsonDocument;
@@ -267,7 +268,7 @@ public void monitorsSleepAtLeastMinHeartbeatFreqencyMSBetweenChecks() {
private static void assertPoll(final BlockingQueue> queue, @Nullable final Class> allowed, final Set> required)
throws InterruptedException {
- assertPoll(queue, allowed, required, Timeout.startNow(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS));
+ assertPoll(queue, allowed, required, Timeout.started(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS, Timer.start()));
}
private static void assertPoll(final BlockingQueue> queue, @Nullable final Class> allowed, final Set> required,
From 2619552d4c5e6eb5f9b4d6b0b470f47c683ba191 Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Tue, 8 Aug 2023 15:17:45 -0600
Subject: [PATCH 02/11] Remove `Timer.oneOff`
It's no longer needed because of https://github.com/mongodb/specifications/pull/1448/commits/31f29fb6d55ce34c88d248161e7158177620191e
JAVA-5076
---
.../main/com/mongodb/internal/time/Timer.java | 45 -------------------
.../com/mongodb/internal/time/TimerTest.java | 14 +-----
2 files changed, 1 insertion(+), 58 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/time/Timer.java b/driver-core/src/main/com/mongodb/internal/time/Timer.java
index 881574ad46d..4ef23d04fd4 100644
--- a/driver-core/src/main/com/mongodb/internal/time/Timer.java
+++ b/driver-core/src/main/com/mongodb/internal/time/Timer.java
@@ -16,7 +16,6 @@
package com.mongodb.internal.time;
import com.mongodb.annotations.Immutable;
-import com.mongodb.annotations.NotThreadSafe;
import com.mongodb.internal.VisibleForTesting;
import java.util.concurrent.TimeUnit;
@@ -65,13 +64,6 @@ public long elapsed(final TimeUnit unit) {
return unit.convert(elapsedNanos(System.nanoTime()), NANOSECONDS);
}
- /**
- * Creates a new {@link OneOff}.
- */
- public OneOff oneOff() {
- return new OneOff(this);
- }
-
/**
* Returns {@code currentNanos} - {@link #startNanos}:
*
*/
- public static Timeout started(final long durationNanos, final Timer timer) {
+ public static Timeout started(final long durationNanos, final TimePoint at) {
if (durationNanos < 0 || durationNanos == Long.MAX_VALUE) {
return infinite();
} else if (durationNanos == 0) {
return immediate();
} else {
- return new Timeout(durationNanos, timer);
+ return new Timeout(durationNanos, assertNotNull(at));
}
}
/**
- * @see #started(long, Timer)
+ * @see #started(long, TimePoint)
*/
public static Timeout infinite() {
return INFINITE;
}
/**
- * @see #started(long, Timer)
+ * @see #started(long, TimePoint)
*/
public static Timeout immediate() {
return IMMEDIATE;
@@ -97,13 +103,11 @@ public static Timeout immediate() {
* Returns 0 or a positive value.
* 0 means that the timeout has expired.
*
- * @throws AssertionError If the timeout is {@linkplain #isInfinite() infinite} or {@linkplain #isImmediate() immediate},
- * because such timeouts use {@link Timer#useless()}.
+ * @throws AssertionError If the timeout is {@linkplain #isInfinite() infinite} or {@linkplain #isImmediate() immediate}.
*/
@VisibleForTesting(otherwise = PRIVATE)
- long nonNegativeRemainingNanos(final long currentNanos) {
- assertFalse(isInfinite() || isImmediate());
- return Math.max(0, durationNanos - timer.elapsedNanos(currentNanos));
+ long nonNegativeRemainingNanos(final TimePoint now) {
+ return Math.max(0, durationNanos - now.durationSince(assertNotNull(start)).toNanos());
}
/**
@@ -125,7 +129,7 @@ long nonNegativeRemainingNanos(final long currentNanos) {
*/
public long remaining(final TimeUnit unit) {
assertFalse(isInfinite());
- return isImmediate() ? 0 : convertRoundUp(nonNegativeRemainingNanos(System.nanoTime()), unit);
+ return isImmediate() ? 0 : convertRoundUp(nonNegativeRemainingNanos(TimePoint.now()), unit);
}
/**
@@ -180,12 +184,12 @@ public boolean equals(final Object o) {
return false;
}
Timeout other = (Timeout) o;
- return durationNanos == other.durationNanos && timer == other.timer;
+ return durationNanos == other.durationNanos && start == other.start;
}
@Override
public int hashCode() {
- return Objects.hash(durationNanos, timer);
+ return Objects.hash(durationNanos, start);
}
/**
@@ -197,7 +201,7 @@ public int hashCode() {
public String toString() {
return "Timeout{"
+ "durationNanos=" + durationNanos
- + ", timer=" + timer
+ + ", start=" + start
+ '}';
}
@@ -221,11 +225,6 @@ long durationNanos() {
return durationNanos;
}
- @VisibleForTesting(otherwise = PRIVATE)
- Timer timer() {
- return timer;
- }
-
@VisibleForTesting(otherwise = PRIVATE)
static long convertRoundUp(final long nonNegativeNanos, final TimeUnit unit) {
assertTrue(nonNegativeNanos >= 0);
diff --git a/driver-core/src/main/com/mongodb/internal/time/Timer.java b/driver-core/src/main/com/mongodb/internal/time/Timer.java
deleted file mode 100644
index 4ef23d04fd4..00000000000
--- a/driver-core/src/main/com/mongodb/internal/time/Timer.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2008-present MongoDB, Inc.
- *
- * Licensed 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
- *
- * 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,
- * 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 com.mongodb.internal.time;
-
-import com.mongodb.annotations.Immutable;
-import com.mongodb.internal.VisibleForTesting;
-
-import java.util.concurrent.TimeUnit;
-
-import static com.mongodb.assertions.Assertions.assertTrue;
-import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-
-/**
- * A value-based class
- * for tracking elapsed time. The maximum duration this class can measure reliably is {@link Long#MAX_VALUE} nanoseconds,
- * which is more than 292 years. The class must not be used to measure longer durations.
- *
- * This class is not part of the public API and may be removed or changed at any time.
- */
-@Immutable
-public final class Timer {
- private static final Timer USELESS = new Timer(0);
-
- private final long startNanos;
-
- private Timer(final long startNanos) {
- this.startNanos = startNanos;
- }
-
- /**
- * Returns a newly started {@link Timer}.
- */
- public static Timer start() {
- return start(System.nanoTime());
- }
-
- /**
- * Returns a {@link Timer} started at an unspecified instant.
- */
- public static Timer useless() {
- return USELESS;
- }
-
- /**
- * The duration in {@code unit}s measured by this {@link Timer}.
- *
- * @param unit {@link TimeUnit#convert(long, TimeUnit)} specifies how the conversion from nanoseconds to {@code timeUnit} is done.
- */
- public long elapsed(final TimeUnit unit) {
- return unit.convert(elapsedNanos(System.nanoTime()), NANOSECONDS);
- }
-
- /**
- * Returns {@code currentNanos} - {@link #startNanos}:
- *
- * -
- * A negative value means either of the following
- *
- * - the clock from which {@code currentNanos} was read jumped backwards,
- * in which case the behaviour of this class is undefined;
- * - (n * 263 - 1; (n + 1) * 263)(*) nanoseconds has elapsed.
- *
- *
- * -
- * 0 means either of the following
- *
- * - 0 nanoseconds has elapsed;
- * - (n + 1) * 263(*) nanoseconds has elapsed.
- *
- * This class interprets 0 value as 0 elapsed nanoseconds.
- *
- * -
- * A positive value means either of the following
- *
- * - this number of nanoseconds has elapsed;
- * - ((n + 1) * 263; (n + 2) * 263 - 1](*) nanoseconds has elapsed.
- *
- * This class interprets a positive value as the number of elapsed nanoseconds.
- *
- *
- *
- * (*) n is positive and odd.
- */
- long elapsedNanos(final long currentNanos) {
- long elapsedNanos = currentNanos - startNanos;
- assertTrue(elapsedNanos >= 0);
- return elapsedNanos;
- }
-
- @VisibleForTesting(otherwise = PRIVATE)
- static Timer start(final long startNanos) {
- return new Timer(startNanos);
- }
-
- @VisibleForTesting(otherwise = PRIVATE)
- long startNanos() {
- return startNanos;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- final Timer timer = (Timer) o;
- return startNanos == timer.startNanos;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(startNanos);
- }
-
- @Override
- public String toString() {
- return "Timer{"
- + "startNanos=" + startNanos
- + '}';
- }
-}
diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
index 432d8379d17..4db734c3a68 100644
--- a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
+++ b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
@@ -31,7 +31,7 @@
import com.mongodb.internal.inject.EmptyProvider;
import com.mongodb.internal.inject.OptionalProvider;
import com.mongodb.internal.inject.SameObjectProvider;
-import com.mongodb.internal.time.Timer;
+import com.mongodb.internal.time.TimePoint;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
@@ -523,7 +523,7 @@ private static void useConcurrently(final DefaultConnectionPool pool, final int
}
};
Collection> tasks = new ArrayList<>();
- Timeout duration = Timeout.started(durationNanos, Timer.start());
+ Timeout duration = Timeout.started(durationNanos, TimePoint.now());
for (int i = 0; i < concurrentUsersCount; i++) {
if ((checkoutSync && checkoutAsync) ? i % 2 == 0 : checkoutSync) {//check out synchronously and check in
tasks.add(executor.submit(() -> {
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
new file mode 100644
index 00000000000..9b931fea7d1
--- /dev/null
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * Licensed 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
+ *
+ * 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,
+ * 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 com.mongodb.internal.time;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+final class TimePointTest {
+ @ParameterizedTest
+ @MethodSource("earlierNanosAndNanosArguments")
+ void durationSince(final long earlierNanos, final long nanos) {
+ TimePoint earlierTimePoint = TimePoint.at(earlierNanos);
+ TimePoint timePoint = TimePoint.at(nanos);
+ assertEquals(nanos - earlierNanos, timePoint.durationSince(earlierTimePoint).toNanos());
+ }
+
+ @ParameterizedTest
+ @MethodSource("nanosAndDurationsArguments")
+ void add(final long nanos, final long durationNanos) {
+ TimePoint timePoint = TimePoint.at(nanos);
+ Duration duration = Duration.ofNanos(durationNanos);
+ TimePoint computedTimePoint = timePoint.add(duration);
+ if (duration.isNegative()) {
+ assertEquals(duration.negated(), timePoint.durationSince(computedTimePoint));
+ } else {
+ assertEquals(duration, computedTimePoint.durationSince(timePoint));
+ }
+ }
+
+ private static Stream nanosAndDurationsArguments() {
+ Collection nanos = asList(Long.MIN_VALUE, Long.MIN_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ Collection durations = asList(-Long.MAX_VALUE, -Long.MAX_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ return nanos.stream()
+ .flatMap(nano -> durations.stream()
+ .map(duration -> arguments(nano, duration)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("earlierNanosAndNanosArguments")
+ void compareTo(final long earlierNanos, final long nanos) {
+ TimePoint earlierTimePoint = TimePoint.at(earlierNanos);
+ TimePoint timePoint = TimePoint.at(nanos);
+ if (earlierNanos == nanos) {
+ assertEquals(0, earlierTimePoint.compareTo(timePoint));
+ assertEquals(0, timePoint.compareTo(earlierTimePoint));
+ assertEquals(earlierTimePoint, timePoint);
+ assertEquals(timePoint, earlierTimePoint);
+ } else {
+ assertTrue(earlierTimePoint.compareTo(timePoint) < 0);
+ assertTrue(timePoint.compareTo(earlierTimePoint) > 0);
+ assertNotEquals(earlierTimePoint, timePoint);
+ assertNotEquals(timePoint, earlierTimePoint);
+ }
+ }
+
+ private static Stream earlierNanosAndNanosArguments() {
+ Collection earlierNanos = asList(Long.MIN_VALUE, Long.MIN_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ Collection durations = asList(0L, 1L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ return earlierNanos.stream()
+ .flatMap(earlier -> durations.stream()
+ .map(duration -> arguments(earlier, earlier + duration)));
+ }
+
+ @ParameterizedTest
+ @ValueSource(longs = {1, 7, 10, 100, 1000})
+ void nowUsesRealClock(final long sleepDurationMillis) throws InterruptedException {
+ TimePoint timePointLowerBound = TimePoint.at(System.nanoTime());
+ TimePoint timePoint = TimePoint.now();
+ TimePoint timePointUpperBound = TimePoint.at(System.nanoTime());
+ assertTrue(timePoint.compareTo(timePointLowerBound) >= 0, "the point is too early");
+ assertTrue(timePoint.compareTo(timePointUpperBound) <= 0, "the point is too late");
+ Thread.sleep(sleepDurationMillis);
+ Duration durationLowerBound = TimePoint.at(System.nanoTime()).durationSince(timePointUpperBound);
+ Duration duration = TimePoint.now().durationSince(timePoint);
+ Duration durationUpperBound = TimePoint.at(System.nanoTime()).durationSince(timePointLowerBound);
+ assertTrue(duration.compareTo(durationLowerBound) >= 0, "duration is too low");
+ assertTrue(duration.compareTo(durationUpperBound) <= 0, "duration is too high");
+ }
+
+ private TimePointTest() {
+ }
+}
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
index b0d8c63fec4..88314398d4b 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
@@ -21,6 +21,7 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
+import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@@ -38,7 +39,7 @@ void isInfinite() {
assertAll(
() -> assertTrue(Timeout.infinite().isInfinite()),
() -> assertFalse(Timeout.immediate().isInfinite()),
- () -> assertFalse(Timeout.started(1, Timer.start()).isInfinite()));
+ () -> assertFalse(Timeout.started(1, TimePoint.now()).isInfinite()));
}
@Test
@@ -46,31 +47,31 @@ void isImmediate() {
assertAll(
() -> assertTrue(Timeout.immediate().isImmediate()),
() -> assertFalse(Timeout.infinite().isImmediate()),
- () -> assertFalse(Timeout.started(1, Timer.start()).isImmediate()));
+ () -> assertFalse(Timeout.started(1, TimePoint.now()).isImmediate()));
}
@Test
void started() {
- Timer timer = Timer.start();
+ TimePoint timePoint = TimePoint.now();
assertAll(
- () -> assertEquals(Timeout.infinite(), Timeout.started(-1, timer)),
- () -> assertEquals(Timeout.immediate(), Timeout.started(0, timer)),
- () -> assertNotEquals(Timeout.infinite(), Timeout.started(1, timer)),
- () -> assertNotEquals(Timeout.immediate(), Timeout.started(1, timer)),
- () -> assertNotEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE - 1, timer)),
- () -> assertEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE, timer)));
+ () -> assertEquals(Timeout.infinite(), Timeout.started(-1, timePoint)),
+ () -> assertEquals(Timeout.immediate(), Timeout.started(0, timePoint)),
+ () -> assertNotEquals(Timeout.infinite(), Timeout.started(1, timePoint)),
+ () -> assertNotEquals(Timeout.immediate(), Timeout.started(1, timePoint)),
+ () -> assertNotEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE - 1, timePoint)),
+ () -> assertEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE, timePoint)));
}
@ParameterizedTest
@MethodSource("durationArguments")
void startedConvertsUnits(final long duration, final TimeUnit unit) {
- Timer timer = Timer.start();
+ TimePoint timePoint = TimePoint.now();
if (duration < 0) {
- assertTrue(Timeout.started(duration, unit, timer).isInfinite());
+ assertTrue(Timeout.started(duration, unit, timePoint).isInfinite());
} else if (duration == 0) {
- assertTrue(Timeout.started(duration, unit, timer).isImmediate());
+ assertTrue(Timeout.started(duration, unit, timePoint).isImmediate());
} else {
- assertEquals(unit.toNanos(duration), Timeout.started(duration, unit, timer).durationNanos());
+ assertEquals(unit.toNanos(duration), Timeout.started(duration, unit, timePoint).durationNanos());
}
}
@@ -83,7 +84,7 @@ private static Stream durationArguments() {
}
@Test
- void remainingNanosTrivialCases() {
+ void remainingTrivialCases() {
assertAll(
() -> assertThrows(AssertionError.class, () -> Timeout.infinite().remaining(NANOSECONDS)),
() -> assertTrue(Timeout.infinite().remainingOrInfinite(NANOSECONDS) < 0),
@@ -93,13 +94,13 @@ void remainingNanosTrivialCases() {
@ParameterizedTest
@ValueSource(longs = {1, 7, Long.MAX_VALUE / 2, Long.MAX_VALUE - 1})
- void remainingNanos(final long durationNanos) {
- Timeout timeout = Timeout.started(durationNanos, Timer.start());
- long startNanos = timeout.timer().startNanos();
- assertEquals(durationNanos, timeout.nonNegativeRemainingNanos(startNanos));
- assertEquals(Math.max(0, durationNanos - 1), timeout.nonNegativeRemainingNanos(startNanos + 1));
- assertEquals(0, timeout.nonNegativeRemainingNanos(startNanos + durationNanos));
- assertEquals(0, timeout.nonNegativeRemainingNanos(startNanos + durationNanos + 1));
+ void nonNegativeRemainingNanos(final long durationNanos) {
+ TimePoint start = TimePoint.now();
+ Timeout timeout = Timeout.started(durationNanos, start);
+ assertEquals(durationNanos, timeout.nonNegativeRemainingNanos(start));
+ assertEquals(Math.max(0, durationNanos - 1), timeout.nonNegativeRemainingNanos(start.add(Duration.ofNanos(1))));
+ assertEquals(0, timeout.nonNegativeRemainingNanos(start.add(Duration.ofNanos(durationNanos))));
+ assertEquals(0, timeout.nonNegativeRemainingNanos(start.add(Duration.ofNanos(durationNanos + 1))));
}
@Test
@@ -128,22 +129,18 @@ void convertRoundUp() {
@ParameterizedTest
@ValueSource(longs = {1, 7, 10, 100, 1000})
- void usesRealClock(final long durationNanos) {
- long startNanosLowerBound = System.nanoTime();
- Timeout timeout = Timeout.started(durationNanos, Timer.start());
- long startNanosUpperBound = System.nanoTime();
- long startNanos = timeout.timer().startNanos();
- assertTrue(startNanos - startNanosLowerBound >= 0, "started too early");
- assertTrue(startNanos - startNanosUpperBound <= 0, "started too late");
+ void remaining(final long durationNanos) {
+ TimePoint start = TimePoint.now();
+ Timeout timeout = Timeout.started(durationNanos, start);
while (!timeout.expired()) {
- long remainingNanosUpperBound = Math.max(0, durationNanos - (System.nanoTime() - startNanosUpperBound));
+ long remainingNanosUpperBound = Math.max(0, durationNanos - TimePoint.now().durationSince(start).toNanos());
long remainingNanos = timeout.remaining(NANOSECONDS);
- long remainingNanosLowerBound = Math.max(0, durationNanos - (System.nanoTime() - startNanosLowerBound));
+ long remainingNanosLowerBound = Math.max(0, durationNanos - TimePoint.now().durationSince(start).toNanos());
assertTrue(remainingNanos >= remainingNanosLowerBound, "remaining nanos is too low");
assertTrue(remainingNanos <= remainingNanosUpperBound, "remaining nanos is too high");
Thread.yield();
}
- assertTrue(System.nanoTime() - startNanosLowerBound >= durationNanos, "expired too early");
+ assertTrue(TimePoint.now().durationSince(start).toNanos() >= durationNanos, "expired too early");
}
private TimeoutTest() {
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java
deleted file mode 100644
index c55e82eddf2..00000000000
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimerTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2008-present MongoDB, Inc.
- *
- * Licensed 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
- *
- * 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,
- * 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 com.mongodb.internal.time;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-final class TimerTest {
- @Test
- void useless() {
- assertDoesNotThrow(Timer::useless);
- }
-
- @ParameterizedTest
- @ValueSource(longs = {Long.MIN_VALUE, Long.MIN_VALUE / 2, -1, 0, 1, Long.MAX_VALUE / 2, Long.MAX_VALUE})
- void start(final long startNanos) {
- assertEquals(startNanos, Timer.start(startNanos).startNanos());
- }
-
- @ParameterizedTest
- @ValueSource(longs = {0, 1, 7, Long.MAX_VALUE / 2, Long.MAX_VALUE - 1})
- void elapsedNanos(final long currentNanos) {
- assertEquals(currentNanos, Timer.start(0).elapsedNanos(currentNanos));
- }
-
- @ParameterizedTest
- @ValueSource(longs = {1, 7, 10, 100, 1000})
- void usesRealClock(final long sleepDurationMillis) throws InterruptedException {
- long startNanosLowerBound = System.nanoTime();
- Timer timer = Timer.start();
- long startNanosUpperBound = System.nanoTime();
- long startNanos = timer.startNanos();
- assertTrue(startNanos - startNanosLowerBound >= 0, "started too early");
- assertTrue(startNanos - startNanosUpperBound <= 0, "started too late");
- Thread.sleep(sleepDurationMillis);
- long elapsedNanosLowerBound = System.nanoTime() - startNanosUpperBound;
- long elapsedNanos = timer.elapsed(NANOSECONDS);
- long elapsedNanosUpperBound = System.nanoTime() - startNanosLowerBound;
- assertTrue(elapsedNanos >= elapsedNanosLowerBound, "elapsed nanos is too low");
- assertTrue(elapsedNanos <= elapsedNanosUpperBound, "elapsed nanos is too high");
- }
-
- private TimerTest() {
- }
-}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
index 757ca999fd9..d64ed52fb6e 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
@@ -30,7 +30,7 @@
import com.mongodb.internal.time.Timeout;
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
-import com.mongodb.internal.time.Timer;
+import com.mongodb.internal.time.TimePoint;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
import org.bson.BsonDocument;
@@ -268,7 +268,7 @@ public void monitorsSleepAtLeastMinHeartbeatFreqencyMSBetweenChecks() {
private static void assertPoll(final BlockingQueue> queue, @Nullable final Class> allowed, final Set> required)
throws InterruptedException {
- assertPoll(queue, allowed, required, Timeout.started(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS, Timer.start()));
+ assertPoll(queue, allowed, required, Timeout.started(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS, TimePoint.now()));
}
private static void assertPoll(final BlockingQueue> queue, @Nullable final Class> allowed, final Set> required,
From 60915ed257d89d14ef17f04eedcbaf05bf1695a5 Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Tue, 15 Aug 2023 03:04:23 -0600
Subject: [PATCH 04/11] Work around a bug in OpenJDK JDK 8
See https://bugs.openjdk.org/browse/JDK-8146747
JAVA-5105
---
.../test/unit/com/mongodb/internal/time/TimePointTest.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
index 9b931fea7d1..ccafc533aa3 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
@@ -54,7 +54,10 @@ void add(final long nanos, final long durationNanos) {
private static Stream nanosAndDurationsArguments() {
Collection nanos = asList(Long.MIN_VALUE, Long.MIN_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
- Collection durations = asList(-Long.MAX_VALUE, -Long.MAX_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ Collection durations = asList(
+ // Using `-Long.MAX_VALUE` results in `ArithmeticException` in OpenJDK JDK 8 because of https://bugs.openjdk.org/browse/JDK-8146747.
+ // This was fixed in OpenJDK JDK 9.
+ -Long.MAX_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
return nanos.stream()
.flatMap(nano -> durations.stream()
.map(duration -> arguments(nano, duration)));
From e89fd4c321e0a525696e16faf47e087911fdcc7e Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Tue, 15 Aug 2023 10:18:36 -0600
Subject: [PATCH 05/11] Improve the wording
JAVA-5105
---
.../src/main/com/mongodb/internal/time/TimePoint.java | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
index d2d1522c21c..d151b66c96e 100644
--- a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
+++ b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
@@ -26,7 +26,7 @@
/**
* A value-based class
- * representing a point on a timeline. The origin on this timeline has no known relation to the
+ * representing a point on a timeline. The origin of this timeline has no known relation to the
* {@linkplain Clock#systemUTC() system clock}. The same timeline is used by all {@link TimePoint}s within the same process.
*
* Methods operating on a pair of {@link TimePoint}s,
@@ -87,10 +87,11 @@ public int compareTo(final TimePoint o) {
*
* A negative value means either of the following
*
- * - the clock from which {@link #nanos} was read jumped backwards, which must not happen;
+ * - {@code earlierNanos} is not earlier than {@link #nanos},
+ * for example, because the clock from which {@code earlierNanos}/{@link #nanos} were read jumped backwards;
* - (n * 263 - 1; (n + 1) * 263)(*) nanoseconds has elapsed.
*
- * This method interprets a negative value as {@link #nanos} being earlier than {@code earlierNanos}.
+ * This method throws an {@link AssertionError} if this happens.
*
*
* 0 means either of the following
From 15c2257c052f628d8d6d2acd9d79a1d124c54b0e Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Wed, 16 Aug 2023 17:36:50 -0600
Subject: [PATCH 06/11] Simplify code, improve tests
Instead of having an assertion in `TimePoint` that may detect when a monotonic timer
jumps back in time (the OS implementations of monotonic timers are notorious for having bugs,
but there is nothing we can do with that),
we better allow measuring passing both earlier and later points in `durationSince`.
JAVA-5105
---
.../com/mongodb/internal/time/TimePoint.java | 58 +++-----------
.../mongodb/internal/time/TimePointTest.java | 75 ++++++++-----------
2 files changed, 45 insertions(+), 88 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
index d151b66c96e..764820c3cfc 100644
--- a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
+++ b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
@@ -21,7 +21,6 @@
import java.time.Clock;
import java.time.Duration;
-import static com.mongodb.assertions.Assertions.assertTrue;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
/**
@@ -58,65 +57,32 @@ static TimePoint at(final long nanos) {
}
/**
- * The {@link Duration} between this {@link TimePoint} and the {@code earlier} one.
- *
- * @param earlier A {@link TimePoint} that is {@linkplain #compareTo(TimePoint) not later} than this one.
+ * The {@link Duration} between this {@link TimePoint} and {@code t}.
+ * A {@linkplain Duration#isNegative() negative} {@link Duration} means that
+ * this {@link TimePoint} is {@linkplain #compareTo(TimePoint) earlier} than {@code t}.
*/
- public Duration durationSince(final TimePoint earlier) {
- return Duration.ofNanos(durationNanosSince(earlier.nanos));
+ public Duration durationSince(final TimePoint t) {
+ return Duration.ofNanos(nanos - t.nanos);
}
/**
* Returns a {@link TimePoint} that is {@code duration} away from this one.
*
- * @param duration A duration that may also be {@linkplain Duration#isNegative() negative} or {@linkplain Duration#isZero() zero}.
+ * @param duration A duration that may also be {@linkplain Duration#isNegative() negative}.
*/
public TimePoint add(final Duration duration) {
long durationNanos = duration.toNanos();
return TimePoint.at(nanos + durationNanos);
}
- @Override
- public int compareTo(final TimePoint o) {
- return Long.signum(nanos - o.nanos);
- }
-
/**
- * Returns {@link #nanos} - {@code earlierNanos} if this difference is non-negative:
- *
- * -
- * A negative value means either of the following
- *
- * - {@code earlierNanos} is not earlier than {@link #nanos},
- * for example, because the clock from which {@code earlierNanos}/{@link #nanos} were read jumped backwards;
- * - (n * 263 - 1; (n + 1) * 263)(*) nanoseconds has elapsed.
- *
- * This method throws an {@link AssertionError} if this happens.
- *
- * -
- * 0 means either of the following
- *
- * - 0 nanoseconds has elapsed;
- * - (n + 1) * 263(*) nanoseconds has elapsed.
- *
- * This method interprets 0 value as 0 elapsed nanoseconds.
- *
- * -
- * A positive value means either of the following
- *
- * - this number of nanoseconds has elapsed;
- * - ((n + 1) * 263; (n + 2) * 263 - 1](*) nanoseconds has elapsed.
- *
- * This method interprets a positive value as the number of elapsed nanoseconds.
- *
- *
- *
- * (*) n is positive and odd.
+ * If this {@link TimePoint} is less/greater than {@code t}, then it is earlier/later than {@code t}.
+ *
+ * {@inheritDoc}
*/
- private long durationNanosSince(final long earlierNanos) {
- long durationNanos = nanos - earlierNanos;
- assertTrue(durationNanos >= 0);
- return durationNanos;
+ @Override
+ public int compareTo(final TimePoint t) {
+ return Long.signum(nanos - t.nanos);
}
@Override
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
index ccafc533aa3..b220c39e9e6 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
@@ -15,10 +15,10 @@
*/
package com.mongodb.internal.time;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.ValueSource;
import java.time.Duration;
import java.util.Collection;
@@ -26,41 +26,30 @@
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
final class TimePointTest {
+ @Test
+ void now() {
+ TimePoint timePointLowerBound = TimePoint.at(System.nanoTime());
+ TimePoint timePoint = TimePoint.now();
+ TimePoint timePointUpperBound = TimePoint.at(System.nanoTime());
+ assertTrue(timePoint.compareTo(timePointLowerBound) >= 0, "the point is too early");
+ assertTrue(timePoint.compareTo(timePointUpperBound) <= 0, "the point is too late");
+ }
+
@ParameterizedTest
@MethodSource("earlierNanosAndNanosArguments")
void durationSince(final long earlierNanos, final long nanos) {
+ Duration expectedDuration = Duration.ofNanos(nanos - earlierNanos);
TimePoint earlierTimePoint = TimePoint.at(earlierNanos);
TimePoint timePoint = TimePoint.at(nanos);
- assertEquals(nanos - earlierNanos, timePoint.durationSince(earlierTimePoint).toNanos());
- }
-
- @ParameterizedTest
- @MethodSource("nanosAndDurationsArguments")
- void add(final long nanos, final long durationNanos) {
- TimePoint timePoint = TimePoint.at(nanos);
- Duration duration = Duration.ofNanos(durationNanos);
- TimePoint computedTimePoint = timePoint.add(duration);
- if (duration.isNegative()) {
- assertEquals(duration.negated(), timePoint.durationSince(computedTimePoint));
- } else {
- assertEquals(duration, computedTimePoint.durationSince(timePoint));
- }
- }
-
- private static Stream nanosAndDurationsArguments() {
- Collection nanos = asList(Long.MIN_VALUE, Long.MIN_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
- Collection durations = asList(
- // Using `-Long.MAX_VALUE` results in `ArithmeticException` in OpenJDK JDK 8 because of https://bugs.openjdk.org/browse/JDK-8146747.
- // This was fixed in OpenJDK JDK 9.
- -Long.MAX_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
- return nanos.stream()
- .flatMap(nano -> durations.stream()
- .map(duration -> arguments(nano, duration)));
+ assertFalse(expectedDuration.isNegative());
+ assertEquals(expectedDuration, timePoint.durationSince(earlierTimePoint));
+ assertEquals(expectedDuration.negated(), earlierTimePoint.durationSince(timePoint));
}
@ParameterizedTest
@@ -83,26 +72,28 @@ void compareTo(final long earlierNanos, final long nanos) {
private static Stream earlierNanosAndNanosArguments() {
Collection earlierNanos = asList(Long.MIN_VALUE, Long.MIN_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
- Collection durations = asList(0L, 1L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ Collection durationsInNanos = asList(0L, 1L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
return earlierNanos.stream()
- .flatMap(earlier -> durations.stream()
- .map(duration -> arguments(earlier, earlier + duration)));
+ .flatMap(earlier -> durationsInNanos.stream()
+ .map(durationNanos -> arguments(earlier, earlier + durationNanos)));
}
@ParameterizedTest
- @ValueSource(longs = {1, 7, 10, 100, 1000})
- void nowUsesRealClock(final long sleepDurationMillis) throws InterruptedException {
- TimePoint timePointLowerBound = TimePoint.at(System.nanoTime());
- TimePoint timePoint = TimePoint.now();
- TimePoint timePointUpperBound = TimePoint.at(System.nanoTime());
- assertTrue(timePoint.compareTo(timePointLowerBound) >= 0, "the point is too early");
- assertTrue(timePoint.compareTo(timePointUpperBound) <= 0, "the point is too late");
- Thread.sleep(sleepDurationMillis);
- Duration durationLowerBound = TimePoint.at(System.nanoTime()).durationSince(timePointUpperBound);
- Duration duration = TimePoint.now().durationSince(timePoint);
- Duration durationUpperBound = TimePoint.at(System.nanoTime()).durationSince(timePointLowerBound);
- assertTrue(duration.compareTo(durationLowerBound) >= 0, "duration is too low");
- assertTrue(duration.compareTo(durationUpperBound) <= 0, "duration is too high");
+ @MethodSource("nanosAndDurationsArguments")
+ void add(final long nanos, final Duration duration) {
+ TimePoint timePoint = TimePoint.at(nanos);
+ assertEquals(duration, timePoint.add(duration).durationSince(timePoint));
+ }
+
+ private static Stream nanosAndDurationsArguments() {
+ Collection nanos = asList(Long.MIN_VALUE, Long.MIN_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ Collection durationsInNanos = asList(
+ // Using `-Long.MAX_VALUE` results in `ArithmeticException` in OpenJDK JDK 8 because of https://bugs.openjdk.org/browse/JDK-8146747.
+ // This was fixed in OpenJDK JDK 9.
+ -Long.MAX_VALUE / 2, 0L, Long.MAX_VALUE / 2, Long.MAX_VALUE);
+ return nanos.stream()
+ .flatMap(nano -> durationsInNanos.stream()
+ .map(durationNanos -> arguments(nano, Duration.ofNanos(durationNanos))));
}
private TimePointTest() {
From f61b569c96525e3ff456eb1d01db7f2c844f7be4 Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Wed, 16 Aug 2023 22:01:04 -0600
Subject: [PATCH 07/11] Reinstate `Timeout.startNow`
JAVA-5105
---
.../connection/DefaultConnectionPool.java | 4 +-
.../com/mongodb/internal/time/Timeout.java | 29 +++++++++++--
.../connection/DefaultConnectionPoolTest.java | 3 +-
.../mongodb/internal/time/TimeoutTest.java | 41 ++++++++++++++++---
...erverDiscoveryAndMonitoringProseTests.java | 3 +-
5 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java
index 416245c60b8..2a6d29b0b08 100644
--- a/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java
+++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultConnectionPool.java
@@ -191,7 +191,7 @@ public InternalConnection get(final OperationContext operationContext) {
@Override
public InternalConnection get(final OperationContext operationContext, final long timeoutValue, final TimeUnit timeUnit) {
connectionCheckoutStarted(operationContext);
- Timeout timeout = Timeout.started(timeoutValue, timeUnit, TimePoint.now());
+ Timeout timeout = Timeout.startNow(timeoutValue, timeUnit);
try {
stateAndGeneration.throwIfClosedOrPaused();
PooledConnection connection = getPooledConnection(timeout);
@@ -209,7 +209,7 @@ public InternalConnection get(final OperationContext operationContext, final lon
@Override
public void getAsync(final OperationContext operationContext, final SingleResultCallback callback) {
connectionCheckoutStarted(operationContext);
- Timeout timeout = Timeout.started(settings.getMaxWaitTime(NANOSECONDS), TimePoint.now());
+ Timeout timeout = Timeout.startNow(settings.getMaxWaitTime(NANOSECONDS));
SingleResultCallback eventSendingCallback = (connection, failure) -> {
SingleResultCallback errHandlingCallback = errorHandlingCallback(callback, LOGGER);
if (failure == null) {
diff --git a/driver-core/src/main/com/mongodb/internal/time/Timeout.java b/driver-core/src/main/com/mongodb/internal/time/Timeout.java
index 0b20c341b6d..443445c5e82 100644
--- a/driver-core/src/main/com/mongodb/internal/time/Timeout.java
+++ b/driver-core/src/main/com/mongodb/internal/time/Timeout.java
@@ -59,7 +59,6 @@ private Timeout(final long durationNanos, @Nullable final TimePoint start) {
* Note that the contract of this method is also used in some places to specify the behavior of methods that accept
* {@code (long timeout, TimeUnit unit)}, e.g., {@link com.mongodb.internal.connection.ConcurrentPool#get(long, TimeUnit)},
* so it cannot be changed without updating those methods.
- * @see #started(long, TimePoint)
*/
public static Timeout started(final long duration, final TimeUnit unit, final TimePoint at) {
return started(unit.toNanos(duration), assertNotNull(at));
@@ -85,6 +84,22 @@ public static Timeout started(final long durationNanos, final TimePoint at) {
}
}
+ /**
+ * This method acts identically to {@link #started(long, TimeUnit, TimePoint)}
+ * with the {@linkplain TimePoint#now() current} {@link TimePoint} passed to it.
+ */
+ public static Timeout startNow(final long duration, final TimeUnit unit) {
+ return started(duration, unit, TimePoint.now());
+ }
+
+ /**
+ * This method acts identically to {@link #started(long, TimePoint)}
+ * with the {@linkplain TimePoint#now() current} {@link TimePoint} passed to it.
+ */
+ public static Timeout startNow(final long durationNanos) {
+ return started(durationNanos, TimePoint.now());
+ }
+
/**
* @see #started(long, TimePoint)
*/
@@ -106,7 +121,7 @@ public static Timeout immediate() {
* @throws AssertionError If the timeout is {@linkplain #isInfinite() infinite} or {@linkplain #isImmediate() immediate}.
*/
@VisibleForTesting(otherwise = PRIVATE)
- long nonNegativeRemainingNanos(final TimePoint now) {
+ long saturatingRemainingNanos(final TimePoint now) {
return Math.max(0, durationNanos - now.durationSince(assertNotNull(start)).toNanos());
}
@@ -129,7 +144,7 @@ long nonNegativeRemainingNanos(final TimePoint now) {
*/
public long remaining(final TimeUnit unit) {
assertFalse(isInfinite());
- return isImmediate() ? 0 : convertRoundUp(nonNegativeRemainingNanos(TimePoint.now()), unit);
+ return isImmediate() ? 0 : convertRoundUp(saturatingRemainingNanos(TimePoint.now()), unit);
}
/**
@@ -184,7 +199,7 @@ public boolean equals(final Object o) {
return false;
}
Timeout other = (Timeout) o;
- return durationNanos == other.durationNanos && start == other.start;
+ return durationNanos == other.durationNanos && Objects.equals(start, other.start());
}
@Override
@@ -225,6 +240,12 @@ long durationNanos() {
return durationNanos;
}
+ @VisibleForTesting(otherwise = PRIVATE)
+ @Nullable
+ TimePoint start() {
+ return start;
+ }
+
@VisibleForTesting(otherwise = PRIVATE)
static long convertRoundUp(final long nonNegativeNanos, final TimeUnit unit) {
assertTrue(nonNegativeNanos >= 0);
diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
index 4db734c3a68..919e0b130a8 100644
--- a/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
+++ b/driver-core/src/test/functional/com/mongodb/internal/connection/DefaultConnectionPoolTest.java
@@ -31,7 +31,6 @@
import com.mongodb.internal.inject.EmptyProvider;
import com.mongodb.internal.inject.OptionalProvider;
import com.mongodb.internal.inject.SameObjectProvider;
-import com.mongodb.internal.time.TimePoint;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
@@ -523,7 +522,7 @@ private static void useConcurrently(final DefaultConnectionPool pool, final int
}
};
Collection> tasks = new ArrayList<>();
- Timeout duration = Timeout.started(durationNanos, TimePoint.now());
+ Timeout duration = Timeout.startNow(durationNanos);
for (int i = 0; i < concurrentUsersCount; i++) {
if ((checkoutSync && checkoutAsync) ? i % 2 == 0 : checkoutSync) {//check out synchronously and check in
tasks.add(executor.submit(() -> {
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
index 88314398d4b..36e34f6662c 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
@@ -39,6 +39,7 @@ void isInfinite() {
assertAll(
() -> assertTrue(Timeout.infinite().isInfinite()),
() -> assertFalse(Timeout.immediate().isInfinite()),
+ () -> assertFalse(Timeout.startNow(1).isInfinite()),
() -> assertFalse(Timeout.started(1, TimePoint.now()).isInfinite()));
}
@@ -47,6 +48,7 @@ void isImmediate() {
assertAll(
() -> assertTrue(Timeout.immediate().isImmediate()),
() -> assertFalse(Timeout.infinite().isImmediate()),
+ () -> assertFalse(Timeout.startNow(1).isImmediate()),
() -> assertFalse(Timeout.started(1, TimePoint.now()).isImmediate()));
}
@@ -58,10 +60,27 @@ void started() {
() -> assertEquals(Timeout.immediate(), Timeout.started(0, timePoint)),
() -> assertNotEquals(Timeout.infinite(), Timeout.started(1, timePoint)),
() -> assertNotEquals(Timeout.immediate(), Timeout.started(1, timePoint)),
+ () -> assertEquals(1, Timeout.started(1, timePoint).durationNanos()),
+ () -> assertEquals(timePoint, Timeout.started(1, timePoint).start()),
() -> assertNotEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE - 1, timePoint)),
+ () -> assertEquals(Long.MAX_VALUE - 1, Timeout.started(Long.MAX_VALUE - 1, timePoint).durationNanos()),
+ () -> assertEquals(timePoint, Timeout.started(Long.MAX_VALUE - 1, timePoint).start()),
() -> assertEquals(Timeout.infinite(), Timeout.started(Long.MAX_VALUE, timePoint)));
}
+ @Test
+ void startNow() {
+ assertAll(
+ () -> assertEquals(Timeout.infinite(), Timeout.startNow(-1)),
+ () -> assertEquals(Timeout.immediate(), Timeout.startNow(0)),
+ () -> assertNotEquals(Timeout.infinite(), Timeout.startNow(1)),
+ () -> assertNotEquals(Timeout.immediate(), Timeout.startNow(1)),
+ () -> assertEquals(1, Timeout.startNow(1).durationNanos()),
+ () -> assertNotEquals(Timeout.infinite(), Timeout.startNow(Long.MAX_VALUE - 1)),
+ () -> assertEquals(Long.MAX_VALUE - 1, Timeout.startNow(Long.MAX_VALUE - 1).durationNanos()),
+ () -> assertEquals(Timeout.infinite(), Timeout.startNow(Long.MAX_VALUE)));
+ }
+
@ParameterizedTest
@MethodSource("durationArguments")
void startedConvertsUnits(final long duration, final TimeUnit unit) {
@@ -75,6 +94,18 @@ void startedConvertsUnits(final long duration, final TimeUnit unit) {
}
}
+ @ParameterizedTest
+ @MethodSource("durationArguments")
+ void startNowConvertsUnits(final long duration, final TimeUnit unit) {
+ if (duration < 0) {
+ assertTrue(Timeout.startNow(duration, unit).isInfinite());
+ } else if (duration == 0) {
+ assertTrue(Timeout.startNow(duration, unit).isImmediate());
+ } else {
+ assertEquals(unit.toNanos(duration), Timeout.startNow(duration, unit).durationNanos());
+ }
+ }
+
private static Stream durationArguments() {
return Stream.of(TimeUnit.values())
.flatMap(unit -> Stream.of(
@@ -94,13 +125,13 @@ void remainingTrivialCases() {
@ParameterizedTest
@ValueSource(longs = {1, 7, Long.MAX_VALUE / 2, Long.MAX_VALUE - 1})
- void nonNegativeRemainingNanos(final long durationNanos) {
+ void saturatingRemainingNanos(final long durationNanos) {
TimePoint start = TimePoint.now();
Timeout timeout = Timeout.started(durationNanos, start);
- assertEquals(durationNanos, timeout.nonNegativeRemainingNanos(start));
- assertEquals(Math.max(0, durationNanos - 1), timeout.nonNegativeRemainingNanos(start.add(Duration.ofNanos(1))));
- assertEquals(0, timeout.nonNegativeRemainingNanos(start.add(Duration.ofNanos(durationNanos))));
- assertEquals(0, timeout.nonNegativeRemainingNanos(start.add(Duration.ofNanos(durationNanos + 1))));
+ assertEquals(durationNanos, timeout.saturatingRemainingNanos(start));
+ assertEquals(Math.max(0, durationNanos - 1), timeout.saturatingRemainingNanos(start.add(Duration.ofNanos(1))));
+ assertEquals(0, timeout.saturatingRemainingNanos(start.add(Duration.ofNanos(durationNanos))));
+ assertEquals(0, timeout.saturatingRemainingNanos(start.add(Duration.ofNanos(durationNanos + 1))));
}
@Test
diff --git a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
index d64ed52fb6e..1dba06b38df 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/ServerDiscoveryAndMonitoringProseTests.java
@@ -30,7 +30,6 @@
import com.mongodb.internal.time.Timeout;
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
-import com.mongodb.internal.time.TimePoint;
import com.mongodb.lang.Nullable;
import org.bson.BsonArray;
import org.bson.BsonDocument;
@@ -268,7 +267,7 @@ public void monitorsSleepAtLeastMinHeartbeatFreqencyMSBetweenChecks() {
private static void assertPoll(final BlockingQueue> queue, @Nullable final Class> allowed, final Set> required)
throws InterruptedException {
- assertPoll(queue, allowed, required, Timeout.started(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS, TimePoint.now()));
+ assertPoll(queue, allowed, required, Timeout.startNow(TEST_WAIT_TIMEOUT_MILLIS, MILLISECONDS));
}
private static void assertPoll(final BlockingQueue> queue, @Nullable final Class> allowed, final Set> required,
From 31a0f732e0f1c2003e5e36effa305f4d5270bf14 Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Thu, 17 Aug 2023 00:02:06 -0600
Subject: [PATCH 08/11] Change the wording
JAVA-5105
---
driver-core/src/main/com/mongodb/internal/time/TimePoint.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
index 764820c3cfc..e68d9fefaba 100644
--- a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
+++ b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
@@ -59,7 +59,7 @@ static TimePoint at(final long nanos) {
/**
* The {@link Duration} between this {@link TimePoint} and {@code t}.
* A {@linkplain Duration#isNegative() negative} {@link Duration} means that
- * this {@link TimePoint} is {@linkplain #compareTo(TimePoint) earlier} than {@code t}.
+ * this {@link TimePoint} is {@linkplain #compareTo(TimePoint) before} {@code t}.
*/
public Duration durationSince(final TimePoint t) {
return Duration.ofNanos(nanos - t.nanos);
@@ -76,7 +76,7 @@ public TimePoint add(final Duration duration) {
}
/**
- * If this {@link TimePoint} is less/greater than {@code t}, then it is earlier/later than {@code t}.
+ * If this {@link TimePoint} is less/greater than {@code t}, then it is before/after {@code t}.
*
* {@inheritDoc}
*/
From 24307107910a5e4e58b8e50599e4bdc8118cd405 Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Fri, 18 Aug 2023 11:15:31 -0600
Subject: [PATCH 09/11] Get rid of tautology
JAVA-5105
---
.../src/main/com/mongodb/internal/time/Timeout.java | 4 ++--
.../test/unit/com/mongodb/internal/time/TimeoutTest.java | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/driver-core/src/main/com/mongodb/internal/time/Timeout.java b/driver-core/src/main/com/mongodb/internal/time/Timeout.java
index 443445c5e82..f0d4bbf3ea1 100644
--- a/driver-core/src/main/com/mongodb/internal/time/Timeout.java
+++ b/driver-core/src/main/com/mongodb/internal/time/Timeout.java
@@ -121,7 +121,7 @@ public static Timeout immediate() {
* @throws AssertionError If the timeout is {@linkplain #isInfinite() infinite} or {@linkplain #isImmediate() immediate}.
*/
@VisibleForTesting(otherwise = PRIVATE)
- long saturatingRemainingNanos(final TimePoint now) {
+ long remainingNanos(final TimePoint now) {
return Math.max(0, durationNanos - now.durationSince(assertNotNull(start)).toNanos());
}
@@ -144,7 +144,7 @@ long saturatingRemainingNanos(final TimePoint now) {
*/
public long remaining(final TimeUnit unit) {
assertFalse(isInfinite());
- return isImmediate() ? 0 : convertRoundUp(saturatingRemainingNanos(TimePoint.now()), unit);
+ return isImmediate() ? 0 : convertRoundUp(remainingNanos(TimePoint.now()), unit);
}
/**
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
index 36e34f6662c..c3ee823aff2 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
@@ -128,10 +128,10 @@ void remainingTrivialCases() {
void saturatingRemainingNanos(final long durationNanos) {
TimePoint start = TimePoint.now();
Timeout timeout = Timeout.started(durationNanos, start);
- assertEquals(durationNanos, timeout.saturatingRemainingNanos(start));
- assertEquals(Math.max(0, durationNanos - 1), timeout.saturatingRemainingNanos(start.add(Duration.ofNanos(1))));
- assertEquals(0, timeout.saturatingRemainingNanos(start.add(Duration.ofNanos(durationNanos))));
- assertEquals(0, timeout.saturatingRemainingNanos(start.add(Duration.ofNanos(durationNanos + 1))));
+ assertEquals(durationNanos, timeout.remainingNanos(start));
+ assertEquals(Math.max(0, durationNanos - 1), timeout.remainingNanos(start.add(Duration.ofNanos(1))));
+ assertEquals(0, timeout.remainingNanos(start.add(Duration.ofNanos(durationNanos))));
+ assertEquals(0, timeout.remainingNanos(start.add(Duration.ofNanos(durationNanos + 1))));
}
@Test
From 5bb651be6788ab0dba41c91956388b4e2dd147da Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Fri, 18 Aug 2023 11:37:30 -0600
Subject: [PATCH 10/11] Get rid of tautology
JAVA-5105
---
.../src/test/unit/com/mongodb/internal/time/TimeoutTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
index c3ee823aff2..03df92771ac 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimeoutTest.java
@@ -125,7 +125,7 @@ void remainingTrivialCases() {
@ParameterizedTest
@ValueSource(longs = {1, 7, Long.MAX_VALUE / 2, Long.MAX_VALUE - 1})
- void saturatingRemainingNanos(final long durationNanos) {
+ void remainingNanos(final long durationNanos) {
TimePoint start = TimePoint.now();
Timeout timeout = Timeout.started(durationNanos, start);
assertEquals(durationNanos, timeout.remainingNanos(start));
From a639884533b835cbc726227640747b9929420fcc Mon Sep 17 00:00:00 2001
From: Valentin Kovalenko
Date: Fri, 18 Aug 2023 12:19:17 -0600
Subject: [PATCH 11/11] Add `TimePoint.elapsed`
JAVA-5105
---
.../main/com/mongodb/internal/time/TimePoint.java | 12 ++++++++++++
.../com/mongodb/internal/time/TimePointTest.java | 10 ++++++++++
2 files changed, 22 insertions(+)
diff --git a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
index e68d9fefaba..78859802150 100644
--- a/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
+++ b/driver-core/src/main/com/mongodb/internal/time/TimePoint.java
@@ -60,11 +60,23 @@ static TimePoint at(final long nanos) {
* The {@link Duration} between this {@link TimePoint} and {@code t}.
* A {@linkplain Duration#isNegative() negative} {@link Duration} means that
* this {@link TimePoint} is {@linkplain #compareTo(TimePoint) before} {@code t}.
+ *
+ * @see #elapsed()
*/
public Duration durationSince(final TimePoint t) {
return Duration.ofNanos(nanos - t.nanos);
}
+ /**
+ * The {@link Duration} between {@link TimePoint#now()} and this {@link TimePoint}.
+ * This method is functionally equivalent to {@code TimePoint.now().durationSince(this)}.
+ *
+ * @see #durationSince(TimePoint)
+ */
+ public Duration elapsed() {
+ return Duration.ofNanos(System.nanoTime() - nanos);
+ }
+
/**
* Returns a {@link TimePoint} that is {@code duration} away from this one.
*
diff --git a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
index b220c39e9e6..4f331d208a2 100644
--- a/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
+++ b/driver-core/src/test/unit/com/mongodb/internal/time/TimePointTest.java
@@ -41,6 +41,16 @@ void now() {
assertTrue(timePoint.compareTo(timePointUpperBound) <= 0, "the point is too late");
}
+ @Test
+ void elapsed() {
+ TimePoint timePoint = TimePoint.now();
+ Duration elapsedLowerBound = TimePoint.now().durationSince(timePoint);
+ Duration elapsed = timePoint.elapsed();
+ Duration elapsedUpperBound = TimePoint.now().durationSince(timePoint);
+ assertTrue(elapsed.compareTo(elapsedLowerBound) >= 0, "the elapsed is too low");
+ assertTrue(elapsed.compareTo(elapsedUpperBound) <= 0, "the elapsed is too high");
+ }
+
@ParameterizedTest
@MethodSource("earlierNanosAndNanosArguments")
void durationSince(final long earlierNanos, final long nanos) {