Skip to content

Commit 89fc3c0

Browse files
committed
Add BackOffExecution to isolate state
This commit separates the BackOff configuration from an actual execution. BackOffExecution now contains all the state of a particular execution and BackOff is only meant to start (i.e. create) a new execution. The method "reset" has been removed as its no longer necessary: when an execution does not need to be used for a given operation anymore it can be simply discarded. Issue: SPR-11746
1 parent 49040a2 commit 89fc3c0

File tree

10 files changed

+270
-167
lines changed

10 files changed

+270
-167
lines changed

spring-core/src/main/java/org/springframework/util/BackOff.java

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717
package org.springframework.util;
1818

1919
/**
20-
* Indicate the rate at which an operation should be retried.
20+
* Provide a {@link BackOffExecution} that indicates the rate at which
21+
* an operation should be retried.
2122
*
2223
* <p>Users of this interface are expected to use it like this:
2324
*
2425
* <pre class="code">
2526
* {@code
2627
*
27-
* long waitInterval = backOff.nextBackOffMillis();
28-
* if (waitInterval == BackOff.STOP) {
29-
* backOff.reset();
28+
* BackOffExecution exec = backOff.start();
29+
*
30+
* // In the operation recovery/retry loop:
31+
* long waitInterval = exec.nextBackOffMillis();
32+
* if (waitInterval == BackOffExecution.STOP) {
3033
* // do not retry operation
3134
* }
3235
* else {
@@ -35,31 +38,19 @@
3538
* }
3639
* }</pre>
3740
*
38-
* Once the underlying operation has completed successfully, the instance
39-
* <b>must</b> be {@link #reset()} before further use. Due to how back off
40-
* should be used, implementations do not need to be thread-safe.
41+
* Once the underlying operation has completed successfully, the execution
42+
* instance can be simply discarded.
4143
*
4244
* @author Stephane Nicoll
4345
* @since 4.1
46+
* @see BackOffExecution
4447
*/
4548
public interface BackOff {
4649

4750
/**
48-
* Return value of {@link #nextBackOff()} that indicates that the operation
49-
* should not be retried.
50-
*/
51-
long STOP = -1;
52-
53-
/**
54-
* Return the number of milliseconds to wait before retrying the operation
55-
* or {@link #STOP} ({@value #STOP}) to indicate that no further attempt
56-
* should be made for the operation.
57-
*/
58-
long nextBackOff();
59-
60-
/**
61-
* Reset this instance to its original state.
51+
* Start a new back off execution.
52+
* @return a fresh {@link BackOffExecution} ready to be used
6253
*/
63-
void reset();
54+
BackOffExecution start();
6455

6556
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.util;
18+
19+
/**
20+
* Represent a particular back-off execution.
21+
*
22+
* <p>Implementations do not need to be thread safe.
23+
*
24+
* @author Stephane Nicoll
25+
* @since 4.1
26+
* @see org.springframework.util.BackOff
27+
*/
28+
public interface BackOffExecution {
29+
30+
/**
31+
* Return value of {@link #nextBackOff()} that indicates that the operation
32+
* should not be retried.
33+
*/
34+
long STOP = -1;
35+
36+
/**
37+
* Return the number of milliseconds to wait before retrying the operation
38+
* or {@link #STOP} ({@value #STOP}) to indicate that no further attempt
39+
* should be made for the operation.
40+
*/
41+
long nextBackOff();
42+
43+
}

spring-core/src/main/java/org/springframework/util/ExponentialBackOff.java

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
*
4545
* Note that the default max elapsed time is {@link Long#MAX_VALUE}. Use
4646
* {@link #setMaxElapsedTime(long)} to limit the maximum number of time
47-
* that an instance should accumulate before returning {@link BackOff#STOP}.
47+
* that an instance should accumulate before returning
48+
* {@link BackOffExecution#STOP}.
4849
*
4950
* @author Stephane Nicoll
5051
* @since 4.1
@@ -80,9 +81,6 @@ public class ExponentialBackOff implements BackOff {
8081

8182
private long maxElapsedTime = DEFAULT_MAX_ELAPSED_TIME;
8283

83-
private long currentInterval = -1;
84-
85-
private long currentElapsedTime = 0;
8684

8785
/**
8886
* Create an instance with the default settings.
@@ -112,6 +110,13 @@ public void setInitialInterval(long initialInterval) {
112110
this.initialInterval = initialInterval;
113111
}
114112

113+
/**
114+
* Return the initial interval in milliseconds.
115+
*/
116+
public long getInitialInterval() {
117+
return initialInterval;
118+
}
119+
115120
/**
116121
* The value to multiply the current interval with for each retry attempt.
117122
*/
@@ -120,57 +125,46 @@ public void setMultiplier(double multiplier) {
120125
this.multiplier = multiplier;
121126
}
122127

128+
/**
129+
* Return the value to multiply the current interval with for each retry attempt.
130+
*/
131+
public double getMultiplier() {
132+
return multiplier;
133+
}
134+
123135
/**
124136
* The maximum back off time.
125137
*/
126138
public void setMaxInterval(long maxInterval) {
127139
this.maxInterval = maxInterval;
128140
}
129141

142+
/**
143+
* Return the maximum back off time.
144+
*/
145+
public long getMaxInterval() {
146+
return maxInterval;
147+
}
148+
130149
/**
131150
* The maximum elapsed time in milliseconds after which a call to
132-
* {@link #nextBackOff()} returns {@link BackOff#STOP}.
151+
* {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}.
133152
*/
134153
public void setMaxElapsedTime(long maxElapsedTime) {
135154
this.maxElapsedTime = maxElapsedTime;
136155
}
137156

138-
@Override
139-
public long nextBackOff() {
140-
if (currentElapsedTime >= maxElapsedTime) {
141-
return BackOff.STOP;
142-
}
143-
144-
long nextInterval = computeNextInterval();
145-
currentElapsedTime += nextInterval;
146-
return nextInterval;
147-
157+
/**
158+
* Return the maximum elapsed time in milliseconds after which a call to
159+
* {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}.
160+
*/
161+
public long getMaxElapsedTime() {
162+
return maxElapsedTime;
148163
}
149164

150165
@Override
151-
public void reset() {
152-
this.currentInterval = -1;
153-
this.currentElapsedTime = 0;
154-
}
155-
156-
private long computeNextInterval() {
157-
if (this.currentInterval >= this.maxInterval) {
158-
return this.maxInterval;
159-
}
160-
else if (this.currentInterval < 0) {
161-
this.currentInterval = (this.initialInterval < this.maxInterval
162-
? this.initialInterval : this.maxInterval);
163-
}
164-
else {
165-
this.currentInterval = multiplyInterval();
166-
}
167-
return currentInterval;
168-
}
169-
170-
private long multiplyInterval() {
171-
long i = this.currentInterval;
172-
i *= this.multiplier;
173-
return (i > this.maxInterval ? this.maxInterval :i);
166+
public BackOffExecution start() {
167+
return new ExponentialBackOffExecution();
174168
}
175169

176170
private void checkMultiplier(double multiplier) {
@@ -180,14 +174,56 @@ private void checkMultiplier(double multiplier) {
180174
}
181175
}
182176

183-
@Override
184-
public String toString() {
185-
String i = (this.currentInterval < 0 ? "n/a" : this.currentInterval + "ms");
186-
final StringBuilder sb = new StringBuilder("ExponentialBackOff{");
187-
sb.append("currentInterval=").append(i);
188-
sb.append(", multiplier=").append(this.multiplier);
189-
sb.append('}');
190-
return sb.toString();
177+
178+
private class ExponentialBackOffExecution implements BackOffExecution {
179+
180+
private long currentInterval = -1;
181+
182+
private long currentElapsedTime = 0;
183+
184+
@Override
185+
public long nextBackOff() {
186+
if (currentElapsedTime >= maxElapsedTime) {
187+
return BackOffExecution.STOP;
188+
}
189+
190+
long nextInterval = computeNextInterval();
191+
currentElapsedTime += nextInterval;
192+
return nextInterval;
193+
}
194+
195+
private long computeNextInterval() {
196+
long maxInterval = getMaxInterval();
197+
if (this.currentInterval >= maxInterval) {
198+
return maxInterval;
199+
}
200+
else if (this.currentInterval < 0) {
201+
long initialInterval = getInitialInterval();
202+
this.currentInterval = (initialInterval < maxInterval
203+
? initialInterval : maxInterval);
204+
}
205+
else {
206+
this.currentInterval = multiplyInterval(maxInterval);
207+
}
208+
return currentInterval;
209+
}
210+
211+
private long multiplyInterval(long maxInterval) {
212+
long i = this.currentInterval;
213+
i *= getMultiplier();
214+
return (i > maxInterval ? maxInterval : i);
215+
}
216+
217+
218+
@Override
219+
public String toString() {
220+
String i = (this.currentInterval < 0 ? "n/a" : this.currentInterval + "ms");
221+
final StringBuilder sb = new StringBuilder("ExponentialBackOff{");
222+
sb.append("currentInterval=").append(i);
223+
sb.append(", multiplier=").append(getMultiplier());
224+
sb.append('}');
225+
return sb.toString();
226+
}
191227
}
192228

193229
}

spring-core/src/main/java/org/springframework/util/FixedBackOff.java

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ public class FixedBackOff implements BackOff {
3939

4040
private long maxAttempts = UNLIMITED_ATTEMPTS;
4141

42-
private long currentAttempts = 0;
4342

4443
/**
4544
* Create an instance with an interval of {@value #DEFAULT_INTERVAL}
@@ -87,31 +86,38 @@ public long getMaxAttempts() {
8786
}
8887

8988
@Override
90-
public long nextBackOff() {
91-
this.currentAttempts++;
92-
if (this.currentAttempts <= this.maxAttempts) {
93-
return this.interval;
94-
}
95-
else {
96-
return BackOff.STOP;
97-
}
89+
public BackOffExecution start() {
90+
return new FixedBackOffExecution();
9891
}
9992

100-
@Override
101-
public void reset() {
102-
this.currentAttempts = 0;
103-
}
10493

105-
@Override
106-
public String toString() {
107-
final StringBuilder sb = new StringBuilder("FixedBackOff{");
108-
sb.append("interval=").append(this.interval);
109-
String attemptValue = (this.maxAttempts == Long.MAX_VALUE ? "unlimited"
110-
: String.valueOf(this.maxAttempts));
111-
sb.append(", currentAttempts=").append(this.currentAttempts);
112-
sb.append(", maxAttempts=").append(attemptValue);
113-
sb.append('}');
114-
return sb.toString();
94+
private class FixedBackOffExecution implements BackOffExecution {
95+
96+
private long currentAttempts = 0;
97+
98+
@Override
99+
public long nextBackOff() {
100+
this.currentAttempts++;
101+
if (this.currentAttempts <= getMaxAttempts()) {
102+
return getInterval();
103+
}
104+
else {
105+
return BackOffExecution.STOP;
106+
}
107+
}
108+
109+
@Override
110+
public String toString() {
111+
final StringBuilder sb = new StringBuilder("FixedBackOff{");
112+
sb.append("interval=").append(FixedBackOff.this.interval);
113+
String attemptValue = (FixedBackOff.this.maxAttempts == Long.MAX_VALUE ? "unlimited"
114+
: String.valueOf(FixedBackOff.this.maxAttempts));
115+
sb.append(", currentAttempts=").append(this.currentAttempts);
116+
sb.append(", maxAttempts=").append(attemptValue);
117+
sb.append('}');
118+
return sb.toString();
119+
}
120+
115121
}
116122

117123
}

0 commit comments

Comments
 (0)