17
17
package org .springframework .core .retry ;
18
18
19
19
import java .time .Duration ;
20
- import java .util .ArrayList ;
21
- import java .util .List ;
20
+ import java .util .ArrayDeque ;
21
+ import java .util .Deque ;
22
+ import java .util .Iterator ;
22
23
23
- import org .apache .commons .logging .LogFactory ;
24
24
import org .jspecify .annotations .Nullable ;
25
25
26
26
import org .springframework .core .log .LogAccessor ;
27
- import org .springframework .core .retry .support .CompositeRetryListener ;
28
27
import org .springframework .core .retry .support .MaxRetryAttemptsPolicy ;
29
28
import org .springframework .util .Assert ;
30
29
import org .springframework .util .backoff .BackOff ;
48
47
*
49
48
* @author Mahmoud Ben Hassine
50
49
* @author Sam Brannen
50
+ * @author Juergen Hoeller
51
51
* @since 7.0
52
52
* @see RetryOperations
53
53
* @see RetryPolicy
57
57
*/
58
58
public class RetryTemplate implements RetryOperations {
59
59
60
- protected final LogAccessor logger = new LogAccessor (LogFactory . getLog ( getClass ()) );
60
+ private static final LogAccessor logger = new LogAccessor (RetryTemplate . class );
61
61
62
- protected RetryPolicy retryPolicy = new MaxRetryAttemptsPolicy ();
62
+ private RetryPolicy retryPolicy = new MaxRetryAttemptsPolicy ();
63
63
64
- protected BackOff backOffPolicy = new FixedBackOff (Duration .ofSeconds (1 ));
64
+ private BackOff backOffPolicy = new FixedBackOff (Duration .ofSeconds (1 ));
65
65
66
- protected RetryListener retryListener = new RetryListener () {
67
- };
66
+ private RetryListener retryListener = new RetryListener () {};
68
67
69
68
70
69
/**
@@ -121,7 +120,8 @@ public void setBackOffPolicy(BackOff backOffPolicy) {
121
120
122
121
/**
123
122
* Set the {@link RetryListener} to use.
124
- * <p>If multiple listeners are needed, use a {@link CompositeRetryListener}.
123
+ * <p>If multiple listeners are needed, use a
124
+ * {@link org.springframework.core.retry.support.CompositeRetryListener}.
125
125
* <p>Defaults to a <em>no-op</em> implementation.
126
126
* @param retryListener the retry listener to use
127
127
*/
@@ -158,10 +158,26 @@ public void setRetryListener(RetryListener retryListener) {
158
158
// Retry process starts here
159
159
RetryExecution retryExecution = this .retryPolicy .start ();
160
160
BackOffExecution backOffExecution = this .backOffPolicy .start ();
161
- List <Throwable > suppressedExceptions = new ArrayList <>();
161
+ Deque <Throwable > exceptions = new ArrayDeque <>();
162
+ exceptions .add (initialException );
162
163
163
164
Throwable retryException = initialException ;
164
165
while (retryExecution .shouldRetry (retryException )) {
166
+ try {
167
+ long duration = backOffExecution .nextBackOff ();
168
+ if (duration == BackOffExecution .STOP ) {
169
+ break ;
170
+ }
171
+ logger .debug (() -> "Backing off for %dms after retryable operation '%s'"
172
+ .formatted (duration , retryableName ));
173
+ Thread .sleep (duration );
174
+ }
175
+ catch (InterruptedException interruptedException ) {
176
+ Thread .currentThread ().interrupt ();
177
+ throw new RetryException (
178
+ "Unable to back off for retryable operation '%s'" .formatted (retryableName ),
179
+ interruptedException );
180
+ }
165
181
logger .debug (() -> "Preparing to retry operation '%s'" .formatted (retryableName ));
166
182
try {
167
183
this .retryListener .beforeRetry (retryExecution );
@@ -172,29 +188,22 @@ public void setRetryListener(RetryListener retryListener) {
172
188
return result ;
173
189
}
174
190
catch (Throwable currentAttemptException ) {
191
+ logger .debug (() -> "Retry attempt for operation '%s' failed due to '%s'"
192
+ .formatted (retryableName , currentAttemptException ));
175
193
this .retryListener .onRetryFailure (retryExecution , currentAttemptException );
176
- try {
177
- long duration = backOffExecution .nextBackOff ();
178
- logger .debug (() -> "Retryable operation '%s' failed due to '%s'; backing off for %dms"
179
- .formatted (retryableName , currentAttemptException .getMessage (), duration ));
180
- Thread .sleep (duration );
181
- }
182
- catch (InterruptedException interruptedException ) {
183
- Thread .currentThread ().interrupt ();
184
- throw new RetryException (
185
- "Unable to back off for retryable operation '%s'" .formatted (retryableName ),
186
- interruptedException );
187
- }
188
- suppressedExceptions .add (currentAttemptException );
194
+ exceptions .add (currentAttemptException );
189
195
retryException = currentAttemptException ;
190
196
}
191
197
}
198
+
192
199
// The RetryPolicy has exhausted at this point, so we throw a RetryException with the
193
200
// initial exception as the cause and remaining exceptions as suppressed exceptions.
194
201
RetryException finalException = new RetryException (
195
202
"Retry policy for operation '%s' exhausted; aborting execution" .formatted (retryableName ),
196
- initialException );
197
- suppressedExceptions .forEach (finalException ::addSuppressed );
203
+ exceptions .removeLast ());
204
+ for (Iterator <Throwable > it = exceptions .descendingIterator (); it .hasNext ();) {
205
+ finalException .addSuppressed (it .next ());
206
+ }
198
207
this .retryListener .onRetryPolicyExhaustion (retryExecution , finalException );
199
208
throw finalException ;
200
209
}
0 commit comments