-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Remove hasExpired and expiry checks in retries #1418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,8 +51,7 @@ public final class RetryState { | |
|
||
private final LoopState loopState; | ||
private final int attempts; | ||
@Nullable | ||
private final TimeoutContext timeoutContext; | ||
private final boolean retryUntilTimeoutThrowsException; | ||
@Nullable | ||
private Throwable previouslyChosenException; | ||
|
||
|
@@ -63,7 +62,7 @@ public final class RetryState { | |
* If a timeout is not specified in the {@link TimeoutContext#hasTimeoutMS()}, the specified {@code retries} param acts as a fallback | ||
* bound. Otherwise, retries are unbounded until the timeout is reached. | ||
* <p> | ||
* It is possible to provide an additional {@code retryPredicate} in the {@link #doAdvanceOrThrow(Throwable, BiFunction, BiPredicate, boolean)} method, | ||
* It is possible to provide an additional {@code retryPredicate} in the {@link #doAdvanceOrThrow} method, | ||
* which can be used to stop retrying based on a custom condition additionally to {@code retires} and {@link TimeoutContext}. | ||
* </p> | ||
* | ||
|
@@ -87,7 +86,7 @@ public static RetryState withNonRetryableState() { | |
* Creates a {@link RetryState} that does not limit the number of retries. | ||
* The number of attempts is limited iff {@link TimeoutContext#hasTimeoutMS()} is true and timeout has expired. | ||
* <p> | ||
* It is possible to provide an additional {@code retryPredicate} in the {@link #doAdvanceOrThrow(Throwable, BiFunction, BiPredicate, boolean)} method, | ||
* It is possible to provide an additional {@code retryPredicate} in the {@link #doAdvanceOrThrow} method, | ||
* which can be used to stop retrying based on a custom condition additionally to {@code retires} and {@link TimeoutContext}. | ||
* </p> | ||
* | ||
|
@@ -107,7 +106,7 @@ private RetryState(final int retries, @Nullable final TimeoutContext timeoutCont | |
assertTrue(retries >= 0); | ||
loopState = new LoopState(); | ||
attempts = retries == INFINITE_ATTEMPTS ? INFINITE_ATTEMPTS : retries + 1; | ||
this.timeoutContext = timeoutContext; | ||
this.retryUntilTimeoutThrowsException = timeoutContext != null && timeoutContext.hasTimeoutMS(); | ||
} | ||
|
||
/** | ||
|
@@ -198,16 +197,16 @@ private void doAdvanceOrThrow(final Throwable attemptException, | |
* 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). | ||
* | ||
* The isLastAttempt() method checks if the timeoutMS has expired, which could be greater than the computedServerSelectionMS. | ||
* Therefore, it's important to check if the exception is an instance of MongoOperationTimeoutException to detect a timeout. | ||
* It is important to check if the exception is an instance of MongoOperationTimeoutException to detect a timeout. | ||
*/ | ||
if (isLastAttempt() || attemptException instanceof MongoOperationTimeoutException) { | ||
previouslyChosenException = newlyChosenException; | ||
/* | ||
* The function of isLastIteration() is to indicate if retrying has been explicitly halted. Such a stop is not interpreted as | ||
* 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 (hasTimeoutMs() && !loopState.isLastIteration()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for removing these is that it is safe to behave as if a timeout has not expired (even if it has), because it will be detected and thrown by an ensuing blocking operation. This relies on the assumption that all blocking operations will throw when given an expired timeout (this should be the case, because all blocking operations should now be wrapped in Timeout's branching "call" methods). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests were failing. The logic related to timeout exceptions in RetryState is necessary, but complicated to extract. I've restored it, but the isExpired check is still removed (so this still relies on the ensuing blocking operation throwing a timeout exception). |
||
if (retryUntilTimeoutThrowsException && !loopState.isLastIteration()) { | ||
previouslyChosenException = createMongoTimeoutException( | ||
"Retry attempt exceeded the timeout limit.", | ||
previouslyChosenException); | ||
|
@@ -381,16 +380,12 @@ public boolean isLastAttempt() { | |
if (loopState.isLastIteration()){ | ||
return true; | ||
} | ||
if (hasTimeoutMs()) { | ||
return assertNotNull(timeoutContext).hasExpired(); | ||
if (retryUntilTimeoutThrowsException) { | ||
return false; | ||
} | ||
return attempt() == attempts - 1; | ||
} | ||
|
||
private boolean hasTimeoutMs() { | ||
return timeoutContext != null && timeoutContext.hasTimeoutMS(); | ||
} | ||
|
||
/** | ||
* A 0-based attempt number. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 org.junit.Ignore; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
|
@@ -40,7 +41,6 @@ | |
import static org.junit.jupiter.api.Named.named; | ||
import static org.junit.jupiter.params.provider.Arguments.arguments; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
final class RetryStateTest { | ||
private static final TimeoutContext TIMEOUT_CONTEXT_NO_GLOBAL_TIMEOUT = new TimeoutContext(new TimeoutSettings(0L, 0L, | ||
|
@@ -97,17 +97,6 @@ void unlimitedAttemptsAndAdvance(final TimeoutContext timeoutContext) { | |
); | ||
} | ||
|
||
@Test | ||
void unlimitedAttemptsAndAdvanceWithTimeoutException() { | ||
RetryState retryState = new RetryState(TIMEOUT_CONTEXT_EXPIRED_GLOBAL_TIMEOUT); | ||
assertAll( | ||
() -> assertTrue(retryState.isFirstAttempt()), | ||
() -> assertEquals(0, retryState.attempt()), | ||
() -> assertEquals(0, retryState.attempts()) | ||
); | ||
Assertions.assertThrows(MongoOperationTimeoutException.class, () -> advance(retryState)); | ||
} | ||
|
||
@Test | ||
void limitedAttemptsAndAdvance() { | ||
RetryState retryState = RetryState.withNonRetryableState(); | ||
|
@@ -169,14 +158,6 @@ void breakAndThrowIfRetryAndFalse(final TimeoutContext timeoutContext) { | |
assertFalse(retryState.isLastAttempt()); | ||
} | ||
|
||
@Test | ||
void breakAndThrowIfRetryAndFalseWithExpiredTimeout() { | ||
RetryState retryState = new RetryState(TIMEOUT_CONTEXT_EXPIRED_GLOBAL_TIMEOUT); | ||
retryState.breakAndThrowIfRetryAnd(() -> false); | ||
assertTrue(retryState.isLastAttempt()); | ||
assertThrows(MongoOperationTimeoutException.class, () -> advance(retryState)); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource({"infiniteTimeout", "noTimeout"}) | ||
void breakAndThrowIfRetryAndTrue() { | ||
|
@@ -189,10 +170,6 @@ void breakAndThrowIfRetryAndTrue() { | |
@Test | ||
void breakAndThrowIfRetryAndTrueWithExpiredTimeout() { | ||
TimeoutContext tContextMock = mock(TimeoutContext.class); | ||
when(tContextMock.hasTimeoutMS()).thenReturn(true); | ||
when(tContextMock.hasExpired()) | ||
.thenReturn(false) | ||
.thenReturn(true); | ||
|
||
RetryState retryState = new RetryState(tContextMock); | ||
advance(retryState); | ||
|
@@ -354,6 +331,7 @@ void advanceOrThrowPredicateThrowsAfterFirstAttempt(final TimeoutContext timeout | |
})); | ||
} | ||
|
||
@Ignore // TODO (CSOT) update this | ||
@Test | ||
void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { | ||
RetryState retryState = new RetryState(TIMEOUT_CONTEXT_EXPIRED_GLOBAL_TIMEOUT); | ||
|
@@ -439,6 +417,7 @@ void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContex | |
})); | ||
} | ||
|
||
@Ignore // TODO (CSOT) update this | ||
@Test | ||
void advanceOrThrowTransformThrowsTimeoutExceptionAfterFirstAttempt() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test asserts the behavior of error transformation along with timeout handling. Despite the removal of timeout checks within the The same applies to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed on the call with @katcharov, those tests will be ignored and updated later. |
||
RetryState retryState = new RetryState(TIMEOUT_CONTEXT_EXPIRED_GLOBAL_TIMEOUT); | ||
|
Uh oh!
There was an error while loading. Please reload this page.