mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-24 15:49:26 +00:00
Introduce invoke(Supplier) with last original RuntimeException propagated
Closes gh-36052
This commit is contained in:
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.assertj.core.api.ThrowingConsumer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -79,7 +80,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithImmediateSuccess() throws Exception {
|
||||
void retryableWithImmediateSuccess() throws Exception {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
Retryable<String> retryable = () -> {
|
||||
invocationCount.incrementAndGet();
|
||||
@@ -97,7 +98,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithInitialFailureAndZeroRetriesRetryPolicy() {
|
||||
void retryableWithInitialFailureAndZeroRetriesRetryPolicy() {
|
||||
RetryPolicy retryPolicy = throwable -> false; // Zero retries
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
retryTemplate.setRetryListener(retryListener);
|
||||
@@ -121,9 +122,8 @@ class RetryTemplateTests {
|
||||
verifyNoMoreInteractions(retryListener);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void retryWithInitialFailureAndZeroRetriesFixedBackOffPolicy() {
|
||||
void retryableWithInitialFailureAndZeroRetriesFixedBackOffPolicy() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.withMaxRetries(0);
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
@@ -149,7 +149,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder() {
|
||||
void retryableWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder().maxRetries(0).build();
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
@@ -175,7 +175,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithSuccessAfterInitialFailures() throws Exception {
|
||||
void retryableWithSuccessAfterInitialFailures() throws Exception {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
Retryable<String> retryable = () -> {
|
||||
if (invocationCount.incrementAndGet() <= 2) {
|
||||
@@ -201,7 +201,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithExhaustedPolicy() {
|
||||
void retryableWithExhaustedPolicy() {
|
||||
var invocationCount = new AtomicInteger();
|
||||
|
||||
var retryable = new Retryable<>() {
|
||||
@@ -239,7 +239,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithInterruptionDuringSleep() {
|
||||
void retryableWithInterruptionDuringSleep() {
|
||||
Exception exception = new RuntimeException("Boom!");
|
||||
InterruptedException interruptedException = new InterruptedException();
|
||||
|
||||
@@ -269,7 +269,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithFailingRetryableAndMultiplePredicates() {
|
||||
void retryableWithFailingRetryableAndMultiplePredicates() {
|
||||
var invocationCount = new AtomicInteger();
|
||||
var exception = new NumberFormatException("Boom!");
|
||||
|
||||
@@ -316,7 +316,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithExceptionIncludes() {
|
||||
void retryableWithExceptionIncludes() {
|
||||
var invocationCount = new AtomicInteger();
|
||||
|
||||
var retryable = new Retryable<>() {
|
||||
@@ -387,7 +387,7 @@ class RetryTemplateTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@FieldSource("includesAndExcludesRetryPolicies")
|
||||
void retryWithExceptionIncludesAndExcludes(RetryPolicy retryPolicy) {
|
||||
void retryableWithExceptionIncludesAndExcludes(RetryPolicy retryPolicy) {
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
|
||||
var invocationCount = new AtomicInteger();
|
||||
@@ -436,12 +436,105 @@ class RetryTemplateTests {
|
||||
verifyNoMoreInteractions(retryListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
void supplierWithImmediateSuccess() {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
Supplier<String> retryable = () -> {
|
||||
invocationCount.incrementAndGet();
|
||||
return "always succeeds";
|
||||
};
|
||||
|
||||
assertThat(invocationCount).hasValue(0);
|
||||
assertThat(retryTemplate.invoke(retryable)).isEqualTo("always succeeds");
|
||||
assertThat(invocationCount).hasValue(1);
|
||||
|
||||
// RetryListener interactions:
|
||||
inOrder.verify(retryListener).onRetryableExecution(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
argThat(state -> state.isSuccessful() && state.getRetryCount() == 0));
|
||||
verifyNoMoreInteractions(retryListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
void supplierWithSuccessAfterInitialFailures() {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
Supplier<String> retryable = () -> {
|
||||
if (invocationCount.incrementAndGet() <= 2) {
|
||||
throw new CustomException("Boom " + invocationCount.get());
|
||||
}
|
||||
return "finally succeeded";
|
||||
};
|
||||
|
||||
assertThat(invocationCount).hasValue(0);
|
||||
assertThat(retryTemplate.invoke(retryable)).isEqualTo("finally succeeded");
|
||||
assertThat(invocationCount).hasValue(3);
|
||||
|
||||
// RetryListener interactions:
|
||||
inOrder.verify(retryListener).onRetryableExecution(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
any(RetryState.class));
|
||||
inOrder.verify(retryListener).beforeRetry(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())));
|
||||
inOrder.verify(retryListener).onRetryFailure(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
eq(new CustomException("Boom 2")));
|
||||
inOrder.verify(retryListener).onRetryableExecution(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
any(RetryState.class));
|
||||
inOrder.verify(retryListener).beforeRetry(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())));
|
||||
inOrder.verify(retryListener).onRetrySuccess(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
eq("finally succeeded"));
|
||||
inOrder.verify(retryListener).onRetryableExecution(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
argThat(state -> state.isSuccessful() && state.getRetryCount() == 2));
|
||||
verifyNoMoreInteractions(retryListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
void supplierWithExhaustedPolicy() {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
Supplier<String> retryable = () -> {
|
||||
throw new CustomException("Boom " + invocationCount.incrementAndGet());
|
||||
};
|
||||
|
||||
assertThat(invocationCount).hasValue(0);
|
||||
assertThatExceptionOfType(CustomException.class)
|
||||
.isThrownBy(() -> retryTemplate.invoke(retryable))
|
||||
.withMessage("Boom 4")
|
||||
.satisfies(throwable -> {
|
||||
var counter = new AtomicInteger(1);
|
||||
repeat(3, () -> {
|
||||
inOrder.verify(retryListener).onRetryableExecution(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
any(RetryState.class));
|
||||
inOrder.verify(retryListener).beforeRetry(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())));
|
||||
inOrder.verify(retryListener).onRetryFailure(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
eq(new CustomException("Boom " + counter.incrementAndGet())));
|
||||
});
|
||||
inOrder.verify(retryListener).onRetryableExecution(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
argThat(state -> !state.isSuccessful() && state.getRetryCount() == 3));
|
||||
inOrder.verify(retryListener).onRetryPolicyExhaustion(eq(retryPolicy),
|
||||
argThat(r -> r.getName().equals(retryable.getClass().getName())),
|
||||
argThat(t -> t.getMessage().equals("Retry policy for operation '" +
|
||||
retryable.getClass().getName() + "' exhausted; aborting execution")));
|
||||
});
|
||||
// 4 = 1 initial invocation + 3 retry attempts
|
||||
assertThat(invocationCount).hasValue(4);
|
||||
|
||||
verifyNoMoreInteractions(retryListener);
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class TimeoutTests {
|
||||
|
||||
@Test
|
||||
void retryWithImmediateSuccessAndTimeoutExceeded() throws Exception {
|
||||
void retryableWithImmediateSuccessAndTimeoutExceeded() throws Exception {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder().timeout(Duration.ofMillis(10)).build();
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
retryTemplate.setRetryListener(retryListener);
|
||||
@@ -464,7 +557,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithInitialFailureAndZeroRetriesRetryPolicyAndTimeoutExceeded() {
|
||||
void retryableWithInitialFailureAndZeroRetriesRetryPolicyAndTimeoutExceeded() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder()
|
||||
.timeout(Duration.ofMillis(10))
|
||||
.predicate(throwable -> false) // Zero retries
|
||||
@@ -492,7 +585,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithTimeoutExceededAfterInitialFailure() {
|
||||
void retryableWithTimeoutExceededAfterInitialFailure() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder()
|
||||
.timeout(Duration.ofMillis(10))
|
||||
.delay(Duration.ZERO)
|
||||
@@ -521,7 +614,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithTimeoutExceededAfterFirstDelayButBeforeFirstRetry() {
|
||||
void retryableWithTimeoutExceededAfterFirstDelayButBeforeFirstRetry() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder()
|
||||
.timeout(Duration.ofMillis(20))
|
||||
.delay(Duration.ofMillis(100)) // Delay > Timeout
|
||||
@@ -552,7 +645,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithTimeoutExceededAfterFirstRetry() {
|
||||
void retryableWithTimeoutExceededAfterFirstRetry() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder()
|
||||
.timeout(Duration.ofMillis(20))
|
||||
.delay(Duration.ZERO)
|
||||
@@ -589,7 +682,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithTimeoutExceededAfterSecondRetry() {
|
||||
void retryableWithTimeoutExceededAfterSecondRetry() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder()
|
||||
.timeout(Duration.ofMillis(20))
|
||||
.delay(Duration.ZERO)
|
||||
|
||||
Reference in New Issue
Block a user