Browse Source

Consistently include exceptions for previous attempts in RetryException

In RetryTemplate, if we encounter an InterruptedException while
sleeping for the configured back-off duration, we throw a
RetryException with the InterruptedException as the cause.

However, in contrast to the specification for RetryException, we do not
currently include the exceptions for previous retry attempts as
suppressed exceptions in the RetryException which is thrown in such
scenarios.

In order to comply with the documented contract for RetryException,
this commit includes exceptions for previous attempts in the
RetryException thrown for an InterruptedException as well.

Closes gh-35434
pull/35447/head
Sam Brannen 3 months ago
parent
commit
7484b9c491
  1. 23
      spring-core/src/main/java/org/springframework/core/retry/RetryException.java
  2. 4
      spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java

23
spring-core/src/main/java/org/springframework/core/retry/RetryException.java

@ -22,13 +22,22 @@ import java.util.Objects; @@ -22,13 +22,22 @@ import java.util.Objects;
/**
* Exception thrown when a {@link RetryPolicy} has been exhausted.
*
* <p>A {@code RetryException} will contain the last exception thrown by the
* {@link Retryable} operation as the {@linkplain #getCause() cause} and any
* exceptions from previous attempts as {@linkplain #getSuppressed() suppressed
* <p>A {@code RetryException} will typically contain the last exception thrown
* by the {@link Retryable} operation as the {@linkplain #getCause() cause} and
* any exceptions from previous attempts as {@linkplain #getSuppressed() suppressed
* exceptions}.
*
* <p>However, if an {@link InterruptedException} is encountered while
* {@linkplain Thread#sleep(long) sleeping} for the current
* {@link org.springframework.util.backoff.BackOff BackOff} duration, a
* {@code RetryException} will contain the {@code InterruptedException} as the
* {@linkplain #getCause() cause} and any exceptions from previous attempts to
* invoke the {@code Retryable} operation as {@linkplain #getSuppressed()
* suppressed exceptions}.
*
* @author Mahmoud Ben Hassine
* @author Juergen Hoeller
* @author Sam Brannen
* @since 7.0
* @see RetryOperations
*/
@ -41,7 +50,9 @@ public class RetryException extends Exception { @@ -41,7 +50,9 @@ public class RetryException extends Exception {
/**
* Create a new {@code RetryException} for the supplied message and cause.
* @param message the detail message
* @param cause the last exception thrown by the {@link Retryable} operation
* @param cause the last exception thrown by the {@link Retryable} operation,
* or an {@link InterruptedException} thrown while sleeping for the current
* {@code BackOff} duration
*/
public RetryException(String message, Throwable cause) {
super(message, Objects.requireNonNull(cause, "cause must not be null"));
@ -49,7 +60,9 @@ public class RetryException extends Exception { @@ -49,7 +60,9 @@ public class RetryException extends Exception {
/**
* Get the last exception thrown by the {@link Retryable} operation.
* Get the last exception thrown by the {@link Retryable} operation, or an
* {@link InterruptedException} thrown while sleeping for the current
* {@code BackOff} duration.
*/
@Override
public final Throwable getCause() {

4
spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java

@ -164,9 +164,11 @@ public class RetryTemplate implements RetryOperations { @@ -164,9 +164,11 @@ public class RetryTemplate implements RetryOperations {
}
catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw new RetryException(
RetryException retryException = new RetryException(
"Unable to back off for retryable operation '%s'".formatted(retryableName),
interruptedException);
exceptions.forEach(retryException::addSuppressed);
throw retryException;
}
logger.debug(() -> "Preparing to retry operation '%s'".formatted(retryableName));
try {

Loading…
Cancel
Save