From 8b1804d49e5fef0014b437cf70f856cbaecd867e Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 1 May 2026 19:03:03 -0600 Subject: [PATCH 1/8] Make `RetryState.isLastAttempt` private, and simplify the code JAVA-5956, JAVA-6117, JAVA-6113, JAVA-6119, JAVA-6141 --- .../internal/async/function/RetryState.java | 40 +-- .../RetryingAsyncCallbackSupplier.java | 2 +- .../async/function/RetryStateTest.java | 243 +++++++++--------- 3 files changed, 137 insertions(+), 148 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java index 74504a1d9b5..d458fe68cf2 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java @@ -132,7 +132,7 @@ private RetryState(final int retries, final boolean retryUntilTimeoutThrowsExcep * per attempt and only if all the following is true: * * The {@code retryPredicate} accepts this {@link RetryState} and the exception from the most recent attempt, * and may mutate the exception. The {@linkplain RetryState} advances to represent the state of a new attempt @@ -140,7 +140,7 @@ private RetryState(final int retries, final boolean retryUntilTimeoutThrowsExcep * @throws RuntimeException Iff any of the following is true: * @@ -187,24 +187,10 @@ private void doAdvanceOrThrow(final Throwable attemptException, } assertTrue(!isFirstAttempt() || previouslyChosenException == null); Throwable newlyChosenException = callOnAttemptFailureOperator(previouslyChosenException, attemptException, onlyRuntimeExceptions, onAttemptFailureOperator); - - /* - * A MongoOperationTimeoutException indicates that the operation timed out, either during command execution or server selection. - * The timeout for server selection is determined by the computedServerSelectionMS = min(serverSelectionTimeoutMS, timeoutMS). - * - * It is important to check if the exception is an instance of MongoOperationTimeoutException to detect a timeout. - */ - if (isLastAttempt() || attemptException instanceof MongoOperationTimeoutException) { + if (isLastAttempt(attemptException)) { previouslyChosenException = newlyChosenException; - /* - * The function of isLastIteration() is to indicate if retrying has - * been explicitly halted. Such a stop is not interpreted as - * a timeout exception but as a deliberate cessation of retry attempts. - */ - if (retryUntilTimeoutThrowsException && !loopState.isLastIteration()) { - previouslyChosenException = createMongoTimeoutException( - "Retry attempt exceeded the timeout limit.", - previouslyChosenException); + if (attemptException instanceof MongoOperationTimeoutException) { + previouslyChosenException = createMongoTimeoutException("Retry attempt exceeded the timeout limit.", previouslyChosenException); } throw previouslyChosenException; } else { @@ -365,27 +351,23 @@ public boolean isFirstAttempt() { * An attempt is known to be the last one iff any of the following applies: * * * @see #attempt() */ - public boolean isLastAttempt() { - if (loopState.isLastIteration()) { - return true; - } - if (retryUntilTimeoutThrowsException) { - return false; - } - return attempt() == attempts - 1; + private boolean isLastAttempt(final Throwable attemptException) { + boolean lastIteration = loopState.isLastIteration(); + boolean operationTimeout = retryUntilTimeoutThrowsException && attemptException instanceof MongoOperationTimeoutException; + assertFalse(lastIteration && operationTimeout); + return lastIteration || operationTimeout || attempt() == attempts - 1; } /** * A 0-based attempt number. * * @see #isFirstAttempt() - * @see #isLastAttempt() */ public int attempt() { return loopState.iteration(); diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java index 16f6f2e7086..6ce08513aac 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java @@ -69,7 +69,7 @@ public final class RetryingAsyncCallbackSupplier implements AsyncCallbackSupp * per attempt and only if all the following is true: * * The {@code retryPredicate} accepts this {@link RetryState} and the exception from the most recent attempt, * and may mutate the exception. The {@linkplain RetryState} advances to represent the state of a new attempt diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java index 40a20332492..88db82e6744 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java @@ -21,6 +21,7 @@ import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.async.function.LoopState.AttachmentKey; import com.mongodb.internal.operation.retry.AttachmentKeys; +import com.mongodb.lang.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,11 +29,15 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; import java.util.stream.Stream; 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -73,41 +78,36 @@ static Stream noTimeout() { @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void unlimitedAttemptsAndAdvance(final TimeoutContext timeoutContext) { + RuntimeException attemptException = new RuntimeException(); RetryState retryState = new RetryState(timeoutContext); assertAll( () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()), - () -> assertFalse(retryState.isLastAttempt()) + () -> assertEquals(0, retryState.attempt()) ); - advance(retryState); + retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true); assertAll( () -> assertFalse(retryState.isFirstAttempt()), - () -> assertEquals(1, retryState.attempt()), - () -> assertFalse(retryState.isLastAttempt()) + () -> assertEquals(1, retryState.attempt()) ); retryState.markAsLastAttempt(); assertAll( () -> assertFalse(retryState.isFirstAttempt()), () -> assertEquals(1, retryState.attempt()), - () -> assertTrue(retryState.isLastAttempt()) + () -> assertAdvanceOrThrow(attemptException, retryState, attemptException) ); } @Test void limitedAttemptsAndAdvance() { RetryState retryState = RetryState.withNonRetryableState(); - RuntimeException attemptException = new RuntimeException() { - }; + RuntimeException attemptException = new RuntimeException(); assertAll( () -> assertTrue(retryState.isFirstAttempt()), () -> assertEquals(0, retryState.attempt()), - () -> assertTrue(retryState.isLastAttempt()), - () -> assertThrows(attemptException.getClass(), () -> - retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true)), + () -> assertAdvanceOrThrow(attemptException, retryState, attemptException), // when there is only one attempt, it is both the first and the last one () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()), - () -> assertTrue(retryState.isLastAttempt()) + () -> assertEquals(0, retryState.attempt()) ); } @@ -116,11 +116,8 @@ void limitedAttemptsAndAdvance() { void markAsLastAttemptAdvanceWithRuntimeException(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.markAsLastAttempt(); - assertTrue(retryState.isLastAttempt()); - RuntimeException attemptException = new RuntimeException() { - }; - assertThrows(attemptException.getClass(), - () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> fail())); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> fail()); } @ParameterizedTest(name = "should advance with non-retryable error when marked as last attempt and : ''{0}''") @@ -128,11 +125,8 @@ void markAsLastAttemptAdvanceWithRuntimeException(final TimeoutContext timeoutCo void markAsLastAttemptAdvanceWithError(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.markAsLastAttempt(); - assertTrue(retryState.isLastAttempt()); - Error attemptException = new Error() { - }; - assertThrows(attemptException.getClass(), - () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> fail())); + Error attemptException = new Error(); + assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> fail()); } @ParameterizedTest @@ -140,7 +134,7 @@ void markAsLastAttemptAdvanceWithError(final TimeoutContext timeoutContext) { void breakAndThrowIfRetryAndFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.breakAndThrowIfRetryAnd(Assertions::fail); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -149,7 +143,7 @@ void breakAndThrowIfRetryAndFalse(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); retryState.breakAndThrowIfRetryAnd(() -> false); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -158,17 +152,18 @@ void breakAndThrowIfRetryAndTrue() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_NO_GLOBAL_TIMEOUT); advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); - assertTrue(retryState.isLastAttempt()); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @Test void breakAndThrowIfRetryAndTrueWithExpiredTimeout() { TimeoutContext tContextMock = mock(TimeoutContext.class); - RetryState retryState = new RetryState(tContextMock); advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); - assertTrue(retryState.isLastAttempt()); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @ParameterizedTest @@ -176,12 +171,13 @@ void breakAndThrowIfRetryAndTrueWithExpiredTimeout() { void breakAndThrowIfRetryIfPredicateThrows(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); - RuntimeException e = new RuntimeException() { - }; - assertThrows(e.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { - throw e; - })); - assertFalse(retryState.isLastAttempt()); + RuntimeException exception = new RuntimeException(); + assertEquals( + exception, + assertThrows(exception.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { + throw exception; + }))); + assertAdvanceOrThrow(null, retryState, exception); } @ParameterizedTest @@ -191,7 +187,7 @@ void breakAndCompleteIfRetryAndFirstAttempt(final TimeoutContext timeoutContext) SupplyingCallback callback = new SupplyingCallback<>(); assertFalse(retryState.breakAndCompleteIfRetryAnd(Assertions::fail, callback)); assertFalse(callback.completed()); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -202,7 +198,7 @@ void breakAndCompleteIfRetryAndFalse(final TimeoutContext timeoutContext) { SupplyingCallback callback = new SupplyingCallback<>(); assertFalse(retryState.breakAndCompleteIfRetryAnd(() -> false, callback)); assertFalse(callback.completed()); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -213,7 +209,8 @@ void breakAndCompleteIfRetryAndTrue(final TimeoutContext timeoutContext) { SupplyingCallback callback = new SupplyingCallback<>(); assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> true, callback)); assertThrows(RuntimeException.class, callback::get); - assertTrue(retryState.isLastAttempt()); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @ParameterizedTest @@ -221,23 +218,23 @@ void breakAndCompleteIfRetryAndTrue(final TimeoutContext timeoutContext) { void breakAndCompleteIfRetryAndPredicateThrows(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); - Error e = new Error() { - }; + Error exception = new Error(); SupplyingCallback callback = new SupplyingCallback<>(); assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> { - throw e; + throw exception; }, callback)); - assertThrows(e.getClass(), callback::get); - assertFalse(retryState.isLastAttempt()); + assertEquals( + exception, + assertThrows(exception.getClass(), callback::get)); + assertAdvanceOrThrow(null, retryState, exception); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowPredicateFalse(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException attemptException = new RuntimeException() { - }; - assertThrows(attemptException.getClass(), () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> false)); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> false); } @ParameterizedTest @@ -247,34 +244,30 @@ void advanceReThrowDetectedTimeoutExceptionEvenIfTimeoutInRetryStateIsNotExpired RetryState retryState = new RetryState(timeoutContext); MongoOperationTimeoutException expectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); - MongoOperationTimeoutException actualTimeoutException = - assertThrows(expectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(expectedTimeoutException, - (e1, e2) -> expectedTimeoutException, - (rs, e) -> false)); - - Assertions.assertEquals(actualTimeoutException, expectedTimeoutException); + assertAdvanceOrThrow(expectedTimeoutException, retryState, expectedTimeoutException, + (e1, e2) -> expectedTimeoutException, + (rs, e) -> false); } @Test @DisplayName("should throw timeout exception from retry, when transformer swallows original timeout exception") void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_INFINITE_GLOBAL_TIMEOUT); - RuntimeException previousAttemptException = new RuntimeException() { - }; - MongoOperationTimeoutException expectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); + RuntimeException previousAttemptException = new RuntimeException(); + MongoOperationTimeoutException unexpectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); retryState.advanceOrThrow(previousAttemptException, (e1, e2) -> previousAttemptException, (rs, e) -> true); MongoOperationTimeoutException actualTimeoutException = - assertThrows(expectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(expectedTimeoutException, + assertThrows(unexpectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(unexpectedTimeoutException, (e1, e2) -> previousAttemptException, (rs, e) -> false)); - Assertions.assertNotEquals(actualTimeoutException, expectedTimeoutException); - Assertions.assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); - Assertions.assertEquals(previousAttemptException, actualTimeoutException.getCause(), + assertNotEquals(unexpectedTimeoutException, actualTimeoutException); + assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); + assertEquals(previousAttemptException, actualTimeoutException.getCause(), "Retry timeout exception should have a cause if transformer returned non-timeout exception."); } @@ -283,8 +276,7 @@ void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException( @DisplayName("should throw original timeout exception from retry, when transformer returns original timeout exception") void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutException() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_INFINITE_GLOBAL_TIMEOUT); - RuntimeException previousAttemptException = new RuntimeException() { - }; + RuntimeException previousAttemptException = new RuntimeException(); MongoOperationTimeoutException expectedTimeoutException = TimeoutContext .createMongoTimeoutException("Server selection failed"); @@ -292,44 +284,37 @@ void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutEx (e1, e2) -> previousAttemptException, (rs, e) -> true); - MongoOperationTimeoutException actualTimeoutException = - assertThrows(expectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(expectedTimeoutException, - (e1, e2) -> expectedTimeoutException, - (rs, e) -> false)); - - Assertions.assertEquals(actualTimeoutException, expectedTimeoutException); - Assertions.assertNull(actualTimeoutException.getCause(), - "Original timeout exception should not have a cause if transformer already returned timeout exception."); + assertAdvanceOrThrow(expectedTimeoutException, retryState, expectedTimeoutException, + (e1, e2) -> expectedTimeoutException, + (rs, e) -> false); } @Test void advanceOrThrowPredicateTrueAndLastAttempt() { RetryState retryState = RetryState.withNonRetryableState(); - Error attemptException = new Error() { - }; - assertThrows(attemptException.getClass(), () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true)); + Error attemptException = new Error(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowPredicateThrowsAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException predicateException = new RuntimeException() { - }; - RuntimeException attemptException = new RuntimeException() { - }; - assertThrows(predicateException.getClass(), () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> { - assertTrue(rs.isFirstAttempt()); - assertEquals(attemptException, e); - throw predicateException; - })); + RuntimeException predicateException = new RuntimeException(); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(predicateException, retryState, attemptException, + (e1, e2) -> e2, + (rs, e) -> { + assertTrue(rs.isFirstAttempt()); + assertEquals(attemptException, e); + throw predicateException; + }); } @Test void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_EXPIRED_GLOBAL_TIMEOUT); - RuntimeException predicateException = new RuntimeException() { - }; + RuntimeException predicateException = new RuntimeException(); RuntimeException attemptException = new MongoOperationTimeoutException(EXPECTED_TIMEOUT_MESSAGE); MongoOperationTimeoutException mongoOperationTimeoutException = assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> { @@ -346,58 +331,52 @@ void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowPredicateThrows(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException firstAttemptException = new RuntimeException() { - }; + RuntimeException firstAttemptException = new RuntimeException(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException secondAttemptException = new RuntimeException() { - }; - RuntimeException predicateException = new RuntimeException() { - }; - assertThrows(predicateException.getClass(), () -> retryState.advanceOrThrow(secondAttemptException, (e1, e2) -> e2, (rs, e) -> { - assertEquals(1, rs.attempt()); - assertEquals(secondAttemptException, e); - throw predicateException; - })); + RuntimeException secondAttemptException = new RuntimeException(); + RuntimeException predicateException = new RuntimeException(); + assertAdvanceOrThrow(predicateException, retryState, secondAttemptException, + (e1, e2) -> e2, + (rs, e) -> { + assertEquals(1, rs.attempt()); + assertEquals(secondAttemptException, e); + throw predicateException; + }); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout", "expiredTimeout"}) void advanceOrThrowTransformerThrowsAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException transformerException = new RuntimeException() { - }; - assertThrows(transformerException.getClass(), () -> retryState.advanceOrThrow(new AssertionError(), + RuntimeException transformerException = new RuntimeException(); + assertAdvanceOrThrow(transformerException, retryState, new AssertionError(), (e1, e2) -> { throw transformerException; }, - (rs, e) -> fail())); + (rs, e) -> fail()); } @ParameterizedTest - @MethodSource({"infiniteTimeout", "noTimeout"}) //TODO mock? + @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowTransformerThrows(final TimeoutContext timeoutContext) throws Throwable { RetryState retryState = new RetryState(timeoutContext); - Error firstAttemptException = new Error() { - }; + Error firstAttemptException = new Error(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException transformerException = new RuntimeException() { - }; - assertThrows(transformerException.getClass(), () -> retryState.advanceOrThrow(new AssertionError(), + RuntimeException transformerException = new RuntimeException(); + assertAdvanceOrThrow(transformerException, retryState, new AssertionError(), (e1, e2) -> { throw transformerException; }, - (rs, e) -> fail())); + (rs, e) -> fail()); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException attemptException = new RuntimeException() { - }; - RuntimeException transformerResult = new RuntimeException() { - }; - assertThrows(transformerResult.getClass(), () -> retryState.advanceOrThrow(attemptException, + RuntimeException attemptException = new RuntimeException(); + RuntimeException transformerResult = new RuntimeException(); + assertAdvanceOrThrow(transformerResult, retryState, attemptException, (e1, e2) -> { assertNull(e1); assertEquals(attemptException, e2); @@ -406,7 +385,7 @@ void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContex (rs, e) -> { assertEquals(attemptException, e); return false; - })); + }); } @Test @@ -436,14 +415,11 @@ void advanceOrThrowTransformThrowsTimeoutExceptionAfterFirstAttempt() { @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowTransform(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException firstAttemptException = new RuntimeException() { - }; + RuntimeException firstAttemptException = new RuntimeException(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException secondAttemptException = new RuntimeException() { - }; - RuntimeException transformerResult = new RuntimeException() { - }; - assertThrows(transformerResult.getClass(), () -> retryState.advanceOrThrow(secondAttemptException, + RuntimeException secondAttemptException = new RuntimeException(); + RuntimeException transformerResult = new RuntimeException(); + assertAdvanceOrThrow(transformerResult, retryState, secondAttemptException, (e1, e2) -> { assertEquals(firstAttemptException, e1); assertEquals(secondAttemptException, e2); @@ -452,7 +428,7 @@ void advanceOrThrowTransform(final TimeoutContext timeoutContext) { (rs, e) -> { assertEquals(secondAttemptException, e); return false; - })); + }); } @ParameterizedTest @@ -475,4 +451,35 @@ void attachAndAttachment(final TimeoutContext timeoutContext) { private static void advance(final RetryState retryState) { retryState.advanceOrThrow(new RuntimeException(), (e1, e2) -> e2, (rs, e) -> true); } + + private static void assertAdvanceOrThrow( + @Nullable final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException) { + assertAdvanceOrThrow(expectedException, retryState, attemptException, (rs, e) -> true); + } + + private static void assertAdvanceOrThrow( + @Nullable final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException, + final BiPredicate retryPredicate) { + assertAdvanceOrThrow(expectedException, retryState, attemptException, (e1, e2) -> e2, retryPredicate); + } + + private static void assertAdvanceOrThrow( + @Nullable final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException, + final BinaryOperator onAttemptFailureOperator, + final BiPredicate retryPredicate) { + if (expectedException == null) { + assertDoesNotThrow(() -> retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate)); + } else { + assertEquals( + expectedException, + assertThrows(expectedException.getClass(), () -> + retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate))); + } + } } From 446312cb7205cae794a27c5bd8078e9691440a5c Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Tue, 5 May 2026 14:26:27 -0600 Subject: [PATCH 2/8] Remove an invalid assertions from `RetryState.isLastAttempt` --- .../src/main/com/mongodb/internal/async/function/RetryState.java | 1 - 1 file changed, 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java index d458fe68cf2..bf1c276c599 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java @@ -360,7 +360,6 @@ public boolean isFirstAttempt() { private boolean isLastAttempt(final Throwable attemptException) { boolean lastIteration = loopState.isLastIteration(); boolean operationTimeout = retryUntilTimeoutThrowsException && attemptException instanceof MongoOperationTimeoutException; - assertFalse(lastIteration && operationTimeout); return lastIteration || operationTimeout || attempt() == attempts - 1; } From 65e9bc6f6cfcb7d09f3e770eb1f564a0574474df Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Tue, 5 May 2026 16:41:29 -0600 Subject: [PATCH 3/8] Add the missing parameter for `RetryState.breakAndThrowIfRetryAndTrue` --- .../com/mongodb/internal/async/function/RetryStateTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java index 88db82e6744..e66e1315222 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java @@ -148,8 +148,8 @@ void breakAndThrowIfRetryAndFalse(final TimeoutContext timeoutContext) { @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) - void breakAndThrowIfRetryAndTrue() { - RetryState retryState = new RetryState(TIMEOUT_CONTEXT_NO_GLOBAL_TIMEOUT); + void breakAndThrowIfRetryAndTrue(final TimeoutContext timeoutContext) { + RetryState retryState = new RetryState(timeoutContext); advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); RuntimeException attemptException = new RuntimeException(); From 6f85ee482c7a9705217e1e5dae432974baea0951 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Tue, 5 May 2026 16:45:39 -0600 Subject: [PATCH 4/8] Add an assertion to `RetryState.isLastAttempt`, making sure attempt limit does not conflict with timeout --- .../main/com/mongodb/internal/async/function/RetryState.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java index bf1c276c599..f14f4d2948b 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java @@ -358,9 +358,10 @@ public boolean isFirstAttempt() { * @see #attempt() */ private boolean isLastAttempt(final Throwable attemptException) { - boolean lastIteration = loopState.isLastIteration(); boolean operationTimeout = retryUntilTimeoutThrowsException && attemptException instanceof MongoOperationTimeoutException; - return lastIteration || operationTimeout || attempt() == attempts - 1; + boolean attemptLimit = attempt() == attempts - 1; + assertFalse(operationTimeout && attemptLimit); + return loopState.isLastIteration() || operationTimeout || attemptLimit; } /** From a7d9dfcd051d4004e698e0b3f380e0b9b7e1db64 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Thu, 7 May 2026 08:26:46 -0600 Subject: [PATCH 5/8] Rename assertion methods in `RetryStateTest` --- .../async/function/RetryStateTest.java | 86 ++++++++++--------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java index e66e1315222..023e3136508 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java @@ -21,7 +21,6 @@ import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.async.function.LoopState.AttachmentKey; import com.mongodb.internal.operation.retry.AttachmentKeys; -import com.mongodb.lang.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -93,7 +92,7 @@ void unlimitedAttemptsAndAdvance(final TimeoutContext timeoutContext) { assertAll( () -> assertFalse(retryState.isFirstAttempt()), () -> assertEquals(1, retryState.attempt()), - () -> assertAdvanceOrThrow(attemptException, retryState, attemptException) + () -> assertAdvanceOrThrowThrows(attemptException, retryState, attemptException) ); } @@ -104,7 +103,7 @@ void limitedAttemptsAndAdvance() { assertAll( () -> assertTrue(retryState.isFirstAttempt()), () -> assertEquals(0, retryState.attempt()), - () -> assertAdvanceOrThrow(attemptException, retryState, attemptException), + () -> assertAdvanceOrThrowThrows(attemptException, retryState, attemptException), // when there is only one attempt, it is both the first and the last one () -> assertTrue(retryState.isFirstAttempt()), () -> assertEquals(0, retryState.attempt()) @@ -117,7 +116,7 @@ void markAsLastAttemptAdvanceWithRuntimeException(final TimeoutContext timeoutCo RetryState retryState = new RetryState(timeoutContext); retryState.markAsLastAttempt(); RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> fail()); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException, (rs, e) -> fail()); } @ParameterizedTest(name = "should advance with non-retryable error when marked as last attempt and : ''{0}''") @@ -126,7 +125,7 @@ void markAsLastAttemptAdvanceWithError(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.markAsLastAttempt(); Error attemptException = new Error(); - assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> fail()); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException, (rs, e) -> fail()); } @ParameterizedTest @@ -134,7 +133,7 @@ void markAsLastAttemptAdvanceWithError(final TimeoutContext timeoutContext) { void breakAndThrowIfRetryAndFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.breakAndThrowIfRetryAnd(Assertions::fail); - assertAdvanceOrThrow(null, retryState, new RuntimeException()); + assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); } @ParameterizedTest @@ -143,7 +142,7 @@ void breakAndThrowIfRetryAndFalse(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); retryState.breakAndThrowIfRetryAnd(() -> false); - assertAdvanceOrThrow(null, retryState, new RuntimeException()); + assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); } @ParameterizedTest @@ -153,7 +152,7 @@ void breakAndThrowIfRetryAndTrue(final TimeoutContext timeoutContext) { advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrow(attemptException, retryState, attemptException); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); } @Test @@ -163,7 +162,7 @@ void breakAndThrowIfRetryAndTrueWithExpiredTimeout() { advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrow(attemptException, retryState, attemptException); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); } @ParameterizedTest @@ -177,7 +176,7 @@ void breakAndThrowIfRetryIfPredicateThrows(final TimeoutContext timeoutContext) assertThrows(exception.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { throw exception; }))); - assertAdvanceOrThrow(null, retryState, exception); + assertAdvanceOrThrowDoesNotThrow(retryState, exception); } @ParameterizedTest @@ -187,7 +186,7 @@ void breakAndCompleteIfRetryAndFirstAttempt(final TimeoutContext timeoutContext) SupplyingCallback callback = new SupplyingCallback<>(); assertFalse(retryState.breakAndCompleteIfRetryAnd(Assertions::fail, callback)); assertFalse(callback.completed()); - assertAdvanceOrThrow(null, retryState, new RuntimeException()); + assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); } @ParameterizedTest @@ -198,7 +197,7 @@ void breakAndCompleteIfRetryAndFalse(final TimeoutContext timeoutContext) { SupplyingCallback callback = new SupplyingCallback<>(); assertFalse(retryState.breakAndCompleteIfRetryAnd(() -> false, callback)); assertFalse(callback.completed()); - assertAdvanceOrThrow(null, retryState, new RuntimeException()); + assertAdvanceOrThrowDoesNotThrow(retryState, new RuntimeException()); } @ParameterizedTest @@ -210,7 +209,7 @@ void breakAndCompleteIfRetryAndTrue(final TimeoutContext timeoutContext) { assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> true, callback)); assertThrows(RuntimeException.class, callback::get); RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrow(attemptException, retryState, attemptException); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); } @ParameterizedTest @@ -226,7 +225,7 @@ void breakAndCompleteIfRetryAndPredicateThrows(final TimeoutContext timeoutConte assertEquals( exception, assertThrows(exception.getClass(), callback::get)); - assertAdvanceOrThrow(null, retryState, exception); + assertAdvanceOrThrowDoesNotThrow(retryState, exception); } @ParameterizedTest @@ -234,7 +233,7 @@ void breakAndCompleteIfRetryAndPredicateThrows(final TimeoutContext timeoutConte void advanceOrThrowPredicateFalse(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> false); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException, (rs, e) -> false); } @ParameterizedTest @@ -244,7 +243,7 @@ void advanceReThrowDetectedTimeoutExceptionEvenIfTimeoutInRetryStateIsNotExpired RetryState retryState = new RetryState(timeoutContext); MongoOperationTimeoutException expectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); - assertAdvanceOrThrow(expectedTimeoutException, retryState, expectedTimeoutException, + assertAdvanceOrThrowThrows(expectedTimeoutException, retryState, expectedTimeoutException, (e1, e2) -> expectedTimeoutException, (rs, e) -> false); } @@ -284,7 +283,7 @@ void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutEx (e1, e2) -> previousAttemptException, (rs, e) -> true); - assertAdvanceOrThrow(expectedTimeoutException, retryState, expectedTimeoutException, + assertAdvanceOrThrowThrows(expectedTimeoutException, retryState, expectedTimeoutException, (e1, e2) -> expectedTimeoutException, (rs, e) -> false); } @@ -293,7 +292,7 @@ void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutEx void advanceOrThrowPredicateTrueAndLastAttempt() { RetryState retryState = RetryState.withNonRetryableState(); Error attemptException = new Error(); - assertAdvanceOrThrow(attemptException, retryState, attemptException); + assertAdvanceOrThrowThrows(attemptException, retryState, attemptException); } @ParameterizedTest @@ -302,7 +301,7 @@ void advanceOrThrowPredicateThrowsAfterFirstAttempt(final TimeoutContext timeout RetryState retryState = new RetryState(timeoutContext); RuntimeException predicateException = new RuntimeException(); RuntimeException attemptException = new RuntimeException(); - assertAdvanceOrThrow(predicateException, retryState, attemptException, + assertAdvanceOrThrowThrows(predicateException, retryState, attemptException, (e1, e2) -> e2, (rs, e) -> { assertTrue(rs.isFirstAttempt()); @@ -335,7 +334,7 @@ void advanceOrThrowPredicateThrows(final TimeoutContext timeoutContext) { retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); RuntimeException secondAttemptException = new RuntimeException(); RuntimeException predicateException = new RuntimeException(); - assertAdvanceOrThrow(predicateException, retryState, secondAttemptException, + assertAdvanceOrThrowThrows(predicateException, retryState, secondAttemptException, (e1, e2) -> e2, (rs, e) -> { assertEquals(1, rs.attempt()); @@ -349,7 +348,7 @@ void advanceOrThrowPredicateThrows(final TimeoutContext timeoutContext) { void advanceOrThrowTransformerThrowsAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); RuntimeException transformerException = new RuntimeException(); - assertAdvanceOrThrow(transformerException, retryState, new AssertionError(), + assertAdvanceOrThrowThrows(transformerException, retryState, new AssertionError(), (e1, e2) -> { throw transformerException; }, @@ -363,7 +362,7 @@ void advanceOrThrowTransformerThrows(final TimeoutContext timeoutContext) throws Error firstAttemptException = new Error(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); RuntimeException transformerException = new RuntimeException(); - assertAdvanceOrThrow(transformerException, retryState, new AssertionError(), + assertAdvanceOrThrowThrows(transformerException, retryState, new AssertionError(), (e1, e2) -> { throw transformerException; }, @@ -376,7 +375,7 @@ void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContex RetryState retryState = new RetryState(timeoutContext); RuntimeException attemptException = new RuntimeException(); RuntimeException transformerResult = new RuntimeException(); - assertAdvanceOrThrow(transformerResult, retryState, attemptException, + assertAdvanceOrThrowThrows(transformerResult, retryState, attemptException, (e1, e2) -> { assertNull(e1); assertEquals(attemptException, e2); @@ -419,7 +418,7 @@ void advanceOrThrowTransform(final TimeoutContext timeoutContext) { retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); RuntimeException secondAttemptException = new RuntimeException(); RuntimeException transformerResult = new RuntimeException(); - assertAdvanceOrThrow(transformerResult, retryState, secondAttemptException, + assertAdvanceOrThrowThrows(transformerResult, retryState, secondAttemptException, (e1, e2) -> { assertEquals(firstAttemptException, e1); assertEquals(secondAttemptException, e2); @@ -452,34 +451,41 @@ private static void advance(final RetryState retryState) { retryState.advanceOrThrow(new RuntimeException(), (e1, e2) -> e2, (rs, e) -> true); } - private static void assertAdvanceOrThrow( - @Nullable final Throwable expectedException, + private static void assertAdvanceOrThrowDoesNotThrow( final RetryState retryState, final Throwable attemptException) { - assertAdvanceOrThrow(expectedException, retryState, attemptException, (rs, e) -> true); + assertDoesNotThrow(() -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true)); } - private static void assertAdvanceOrThrow( - @Nullable final Throwable expectedException, + private static void assertAdvanceOrThrowThrows( + final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException) { + assertAdvanceOrThrowThrows( + com.mongodb.assertions.Assertions.assertNotNull(expectedException), + retryState, attemptException, (rs, e) -> true); + } + + private static void assertAdvanceOrThrowThrows( + final Throwable expectedException, final RetryState retryState, final Throwable attemptException, final BiPredicate retryPredicate) { - assertAdvanceOrThrow(expectedException, retryState, attemptException, (e1, e2) -> e2, retryPredicate); + assertAdvanceOrThrowThrows( + com.mongodb.assertions.Assertions.assertNotNull(expectedException), + retryState, attemptException, (e1, e2) -> e2, retryPredicate); } - private static void assertAdvanceOrThrow( - @Nullable final Throwable expectedException, + private static void assertAdvanceOrThrowThrows( + final Throwable expectedException, final RetryState retryState, final Throwable attemptException, final BinaryOperator onAttemptFailureOperator, final BiPredicate retryPredicate) { - if (expectedException == null) { - assertDoesNotThrow(() -> retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate)); - } else { - assertEquals( - expectedException, - assertThrows(expectedException.getClass(), () -> - retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate))); - } + com.mongodb.assertions.Assertions.assertNotNull(expectedException); + assertEquals( + expectedException, + assertThrows(expectedException.getClass(), () -> + retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate))); } } From 11c2479f0296b87652b6092e510847edb5343f42 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Thu, 7 May 2026 08:29:31 -0600 Subject: [PATCH 6/8] Remove the assertion from `RetryState.isLastAttempt` Turned out, `assertFalse(operationTimeout && attemptLimit)` holds only as long as there is `retryUntilTimeoutThrowsException`, which I am going to remove, because it is useless. There is no reason to add an assertion that is known to be removed soon. --- .../src/main/com/mongodb/internal/async/function/RetryState.java | 1 - 1 file changed, 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java index f14f4d2948b..3b8394dae1b 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java @@ -360,7 +360,6 @@ public boolean isFirstAttempt() { private boolean isLastAttempt(final Throwable attemptException) { boolean operationTimeout = retryUntilTimeoutThrowsException && attemptException instanceof MongoOperationTimeoutException; boolean attemptLimit = attempt() == attempts - 1; - assertFalse(operationTimeout && attemptLimit); return loopState.isLastIteration() || operationTimeout || attemptLimit; } From f6db95e7d26dd5739937281c7574c9fb35ec450c Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Thu, 7 May 2026 10:01:22 -0600 Subject: [PATCH 7/8] Improve the variable naming in `RetryStateTest.advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException` --- .../com/mongodb/internal/async/function/RetryStateTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java index 023e3136508..b2f6257be4a 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java @@ -253,18 +253,18 @@ void advanceReThrowDetectedTimeoutExceptionEvenIfTimeoutInRetryStateIsNotExpired void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_INFINITE_GLOBAL_TIMEOUT); RuntimeException previousAttemptException = new RuntimeException(); - MongoOperationTimeoutException unexpectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); + MongoOperationTimeoutException latestAttemptException = TimeoutContext.createMongoTimeoutException("Server selection failed"); retryState.advanceOrThrow(previousAttemptException, (e1, e2) -> previousAttemptException, (rs, e) -> true); MongoOperationTimeoutException actualTimeoutException = - assertThrows(unexpectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(unexpectedTimeoutException, + assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(latestAttemptException, (e1, e2) -> previousAttemptException, (rs, e) -> false)); - assertNotEquals(unexpectedTimeoutException, actualTimeoutException); + assertNotEquals(latestAttemptException, actualTimeoutException); assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); assertEquals(previousAttemptException, actualTimeoutException.getCause(), "Retry timeout exception should have a cause if transformer returned non-timeout exception."); From ebf544fe4c10f6a0ec2e86668e20390e10f8fbfd Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Thu, 7 May 2026 11:09:24 -0600 Subject: [PATCH 8/8] Replace `assertEquals` with `assertSame` when comparing exceptions in `RetryStateTest` --- .../async/function/RetryStateTest.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java index b2f6257be4a..6220753f138 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java @@ -38,6 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -171,7 +172,7 @@ void breakAndThrowIfRetryIfPredicateThrows(final TimeoutContext timeoutContext) RetryState retryState = new RetryState(timeoutContext); advance(retryState); RuntimeException exception = new RuntimeException(); - assertEquals( + assertSame( exception, assertThrows(exception.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { throw exception; @@ -222,7 +223,7 @@ void breakAndCompleteIfRetryAndPredicateThrows(final TimeoutContext timeoutConte assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> { throw exception; }, callback)); - assertEquals( + assertSame( exception, assertThrows(exception.getClass(), callback::get)); assertAdvanceOrThrowDoesNotThrow(retryState, exception); @@ -266,7 +267,7 @@ void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException( assertNotEquals(latestAttemptException, actualTimeoutException); assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); - assertEquals(previousAttemptException, actualTimeoutException.getCause(), + assertSame(previousAttemptException, actualTimeoutException.getCause(), "Retry timeout exception should have a cause if transformer returned non-timeout exception."); } @@ -305,7 +306,7 @@ void advanceOrThrowPredicateThrowsAfterFirstAttempt(final TimeoutContext timeout (e1, e2) -> e2, (rs, e) -> { assertTrue(rs.isFirstAttempt()); - assertEquals(attemptException, e); + assertSame(attemptException, e); throw predicateException; }); } @@ -318,7 +319,7 @@ void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { MongoOperationTimeoutException mongoOperationTimeoutException = assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> { assertTrue(rs.isFirstAttempt()); - assertEquals(attemptException, e); + assertSame(attemptException, e); throw predicateException; })); @@ -338,7 +339,7 @@ void advanceOrThrowPredicateThrows(final TimeoutContext timeoutContext) { (e1, e2) -> e2, (rs, e) -> { assertEquals(1, rs.attempt()); - assertEquals(secondAttemptException, e); + assertSame(secondAttemptException, e); throw predicateException; }); } @@ -378,11 +379,11 @@ void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContex assertAdvanceOrThrowThrows(transformerResult, retryState, attemptException, (e1, e2) -> { assertNull(e1); - assertEquals(attemptException, e2); + assertSame(attemptException, e2); return transformerResult; }, (rs, e) -> { - assertEquals(attemptException, e); + assertSame(attemptException, e); return false; }); } @@ -398,16 +399,16 @@ void advanceOrThrowTransformThrowsTimeoutExceptionAfterFirstAttempt() { assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> { assertNull(e1); - assertEquals(attemptException, e2); + assertSame(attemptException, e2); return transformerResult; }, (rs, e) -> { - assertEquals(attemptException, e); + assertSame(attemptException, e); return false; })); assertEquals(EXPECTED_TIMEOUT_MESSAGE, mongoOperationTimeoutException.getMessage()); - assertEquals(transformerResult, mongoOperationTimeoutException.getCause()); + assertSame(transformerResult, mongoOperationTimeoutException.getCause()); } @ParameterizedTest @@ -420,12 +421,12 @@ void advanceOrThrowTransform(final TimeoutContext timeoutContext) { RuntimeException transformerResult = new RuntimeException(); assertAdvanceOrThrowThrows(transformerResult, retryState, secondAttemptException, (e1, e2) -> { - assertEquals(firstAttemptException, e1); - assertEquals(secondAttemptException, e2); + assertSame(firstAttemptException, e1); + assertSame(secondAttemptException, e2); return transformerResult; }, (rs, e) -> { - assertEquals(secondAttemptException, e); + assertSame(secondAttemptException, e); return false; }); } @@ -483,7 +484,7 @@ private static void assertAdvanceOrThrowThrows( final BinaryOperator onAttemptFailureOperator, final BiPredicate retryPredicate) { com.mongodb.assertions.Assertions.assertNotNull(expectedException); - assertEquals( + assertSame( expectedException, assertThrows(expectedException.getClass(), () -> retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate)));