Skip to content

Commit 7484b9c

Browse files
committed
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
1 parent 7b2730c commit 7484b9c

File tree

2 files changed

+21
-6
lines changed

2 files changed

+21
-6
lines changed

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,22 @@
2222
/**
2323
* Exception thrown when a {@link RetryPolicy} has been exhausted.
2424
*
25-
* <p>A {@code RetryException} will contain the last exception thrown by the
26-
* {@link Retryable} operation as the {@linkplain #getCause() cause} and any
27-
* exceptions from previous attempts as {@linkplain #getSuppressed() suppressed
25+
* <p>A {@code RetryException} will typically contain the last exception thrown
26+
* by the {@link Retryable} operation as the {@linkplain #getCause() cause} and
27+
* any exceptions from previous attempts as {@linkplain #getSuppressed() suppressed
2828
* exceptions}.
2929
*
30+
* <p>However, if an {@link InterruptedException} is encountered while
31+
* {@linkplain Thread#sleep(long) sleeping} for the current
32+
* {@link org.springframework.util.backoff.BackOff BackOff} duration, a
33+
* {@code RetryException} will contain the {@code InterruptedException} as the
34+
* {@linkplain #getCause() cause} and any exceptions from previous attempts to
35+
* invoke the {@code Retryable} operation as {@linkplain #getSuppressed()
36+
* suppressed exceptions}.
37+
*
3038
* @author Mahmoud Ben Hassine
3139
* @author Juergen Hoeller
40+
* @author Sam Brannen
3241
* @since 7.0
3342
* @see RetryOperations
3443
*/
@@ -41,15 +50,19 @@ public class RetryException extends Exception {
4150
/**
4251
* Create a new {@code RetryException} for the supplied message and cause.
4352
* @param message the detail message
44-
* @param cause the last exception thrown by the {@link Retryable} operation
53+
* @param cause the last exception thrown by the {@link Retryable} operation,
54+
* or an {@link InterruptedException} thrown while sleeping for the current
55+
* {@code BackOff} duration
4556
*/
4657
public RetryException(String message, Throwable cause) {
4758
super(message, Objects.requireNonNull(cause, "cause must not be null"));
4859
}
4960

5061

5162
/**
52-
* Get the last exception thrown by the {@link Retryable} operation.
63+
* Get the last exception thrown by the {@link Retryable} operation, or an
64+
* {@link InterruptedException} thrown while sleeping for the current
65+
* {@code BackOff} duration.
5366
*/
5467
@Override
5568
public final Throwable getCause() {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ public RetryListener getRetryListener() {
164164
}
165165
catch (InterruptedException interruptedException) {
166166
Thread.currentThread().interrupt();
167-
throw new RetryException(
167+
RetryException retryException = new RetryException(
168168
"Unable to back off for retryable operation '%s'".formatted(retryableName),
169169
interruptedException);
170+
exceptions.forEach(retryException::addSuppressed);
171+
throw retryException;
170172
}
171173
logger.debug(() -> "Preparing to retry operation '%s'".formatted(retryableName));
172174
try {

0 commit comments

Comments
 (0)