mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-08 16:19:48 +00:00
Match against exception causes in @Retryable and RetryPolicy
Prior to this commit, our @Retryable support as well as a RetryPolicy created by the RetryPolicy.Builder only matched against top-level exceptions when filtering included/excluded exceptions thrown by a @Retryable method or Retryable operation. With this commit, we now match against not only top-level exceptions but also nested causes within those top-level exceptions. This is achieved via the new ExceptionTypeFilter.match(Throwable, boolean) support. See gh-35592 Closes gh-35583
This commit is contained in:
@@ -58,7 +58,7 @@ class DefaultRetryPolicy implements RetryPolicy {
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry(Throwable throwable) {
|
||||
return (this.exceptionFilter.match(throwable) &&
|
||||
return (this.exceptionFilter.match(throwable, true) &&
|
||||
(this.predicate == null || this.predicate.test(throwable)));
|
||||
}
|
||||
|
||||
|
||||
@@ -296,6 +296,9 @@ public interface RetryPolicy {
|
||||
* Specify the types of exceptions for which the {@link RetryPolicy}
|
||||
* should retry a failed operation.
|
||||
* <p>Defaults to all exception types.
|
||||
* <p>The supplied exception types will be matched against an exception
|
||||
* thrown by a failed operation as well as nested
|
||||
* {@linkplain Throwable#getCause() causes}.
|
||||
* <p>If included exception types have already been configured, the supplied
|
||||
* types will be added to the existing list of included types.
|
||||
* <p>This can be combined with other {@code includes}, {@code excludes},
|
||||
@@ -318,6 +321,9 @@ public interface RetryPolicy {
|
||||
* Specify the types of exceptions for which the {@link RetryPolicy}
|
||||
* should retry a failed operation.
|
||||
* <p>Defaults to all exception types.
|
||||
* <p>The supplied exception types will be matched against an exception
|
||||
* thrown by a failed operation as well as nested
|
||||
* {@linkplain Throwable#getCause() causes}.
|
||||
* <p>If included exception types have already been configured, the supplied
|
||||
* types will be added to the existing list of included types.
|
||||
* <p>This can be combined with other {@code includes}, {@code excludes},
|
||||
@@ -337,6 +343,9 @@ public interface RetryPolicy {
|
||||
/**
|
||||
* Specify the types of exceptions for which the {@link RetryPolicy}
|
||||
* should not retry a failed operation.
|
||||
* <p>The supplied exception types will be matched against an exception
|
||||
* thrown by a failed operation as well as nested
|
||||
* {@linkplain Throwable#getCause() causes}.
|
||||
* <p>If excluded exception types have already been configured, the supplied
|
||||
* types will be added to the existing list of excluded types.
|
||||
* <p>This can be combined with {@code includes}, other {@code excludes},
|
||||
@@ -358,6 +367,9 @@ public interface RetryPolicy {
|
||||
/**
|
||||
* Specify the types of exceptions for which the {@link RetryPolicy}
|
||||
* should not retry a failed operation.
|
||||
* <p>The supplied exception types will be matched against an exception
|
||||
* thrown by a failed operation as well as nested
|
||||
* {@linkplain Throwable#getCause() causes}.
|
||||
* <p>If excluded exception types have already been configured, the supplied
|
||||
* types will be added to the existing list of excluded types.
|
||||
* <p>This can be combined with {@code includes}, other {@code excludes},
|
||||
|
||||
@@ -369,7 +369,7 @@ class RetryTemplateTests {
|
||||
public String execute() throws Exception {
|
||||
return switch (invocationCount.incrementAndGet()) {
|
||||
case 1 -> throw new IOException();
|
||||
case 2 -> throw new IOException();
|
||||
case 2 -> throw new RuntimeException(new IOException());
|
||||
case 3 -> throw new CustomFileNotFoundException();
|
||||
default -> "success";
|
||||
};
|
||||
@@ -388,14 +388,15 @@ class RetryTemplateTests {
|
||||
.withCauseExactlyInstanceOf(CustomFileNotFoundException.class)
|
||||
.satisfies(hasSuppressedExceptionsSatisfyingExactly(
|
||||
suppressed1 -> assertThat(suppressed1).isExactlyInstanceOf(IOException.class),
|
||||
suppressed2 -> assertThat(suppressed2).isExactlyInstanceOf(IOException.class)
|
||||
suppressed2 -> assertThat(suppressed2).isExactlyInstanceOf(RuntimeException.class)
|
||||
.hasCauseExactlyInstanceOf(IOException.class)
|
||||
))
|
||||
.satisfies(throwable -> assertThat(throwable.getRetryCount()).isEqualTo(2))
|
||||
.satisfies(throwable -> {
|
||||
repeat(2, () -> {
|
||||
inOrder.verify(retryListener).beforeRetry(retryPolicy, retryable);
|
||||
inOrder.verify(retryListener).onRetryFailure(eq(retryPolicy), eq(retryable), any(IOException.class));
|
||||
});
|
||||
inOrder.verify(retryListener).beforeRetry(retryPolicy, retryable);
|
||||
inOrder.verify(retryListener).onRetryFailure(eq(retryPolicy), eq(retryable), any(RuntimeException.class));
|
||||
inOrder.verify(retryListener).beforeRetry(retryPolicy, retryable);
|
||||
inOrder.verify(retryListener).onRetryFailure(eq(retryPolicy), eq(retryable), any(CustomFileNotFoundException.class));
|
||||
inOrder.verify(retryListener).onRetryPolicyExhaustion(retryPolicy, retryable, throwable);
|
||||
});
|
||||
// 3 = 1 initial invocation + 2 retry attempts
|
||||
|
||||
Reference in New Issue
Block a user