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:
Sam Brannen
2025-10-09 17:55:03 +02:00
parent 58940794cf
commit 97ae5fde7c
9 changed files with 140 additions and 44 deletions
@@ -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