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
This commit is contained in:
@@ -17,16 +17,19 @@
|
||||
package org.springframework.util;
|
||||
|
||||
/**
|
||||
* Indicate the rate at which an operation should be retried.
|
||||
* Provide a {@link BackOffExecution} that indicates the rate at which
|
||||
* an operation should be retried.
|
||||
*
|
||||
* <p>Users of this interface are expected to use it like this:
|
||||
*
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
*
|
||||
* long waitInterval = backOff.nextBackOffMillis();
|
||||
* if (waitInterval == BackOff.STOP) {
|
||||
* backOff.reset();
|
||||
* BackOffExecution exec = backOff.start();
|
||||
*
|
||||
* // In the operation recovery/retry loop:
|
||||
* long waitInterval = exec.nextBackOffMillis();
|
||||
* if (waitInterval == BackOffExecution.STOP) {
|
||||
* // do not retry operation
|
||||
* }
|
||||
* else {
|
||||
@@ -35,31 +38,19 @@ package org.springframework.util;
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* Once the underlying operation has completed successfully, the instance
|
||||
* <b>must</b> be {@link #reset()} before further use. Due to how back off
|
||||
* should be used, implementations do not need to be thread-safe.
|
||||
* Once the underlying operation has completed successfully, the execution
|
||||
* instance can be simply discarded.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.1
|
||||
* @see BackOffExecution
|
||||
*/
|
||||
public interface BackOff {
|
||||
|
||||
/**
|
||||
* Return value of {@link #nextBackOff()} that indicates that the operation
|
||||
* should not be retried.
|
||||
* Start a new back off execution.
|
||||
* @return a fresh {@link BackOffExecution} ready to be used
|
||||
*/
|
||||
long STOP = -1;
|
||||
|
||||
/**
|
||||
* Return the number of milliseconds to wait before retrying the operation
|
||||
* or {@link #STOP} ({@value #STOP}) to indicate that no further attempt
|
||||
* should be made for the operation.
|
||||
*/
|
||||
long nextBackOff();
|
||||
|
||||
/**
|
||||
* Reset this instance to its original state.
|
||||
*/
|
||||
void reset();
|
||||
BackOffExecution start();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
/**
|
||||
* Represent a particular back-off execution.
|
||||
*
|
||||
* <p>Implementations do not need to be thread safe.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.1
|
||||
* @see org.springframework.util.BackOff
|
||||
*/
|
||||
public interface BackOffExecution {
|
||||
|
||||
/**
|
||||
* Return value of {@link #nextBackOff()} that indicates that the operation
|
||||
* should not be retried.
|
||||
*/
|
||||
long STOP = -1;
|
||||
|
||||
/**
|
||||
* Return the number of milliseconds to wait before retrying the operation
|
||||
* or {@link #STOP} ({@value #STOP}) to indicate that no further attempt
|
||||
* should be made for the operation.
|
||||
*/
|
||||
long nextBackOff();
|
||||
|
||||
}
|
||||
@@ -44,7 +44,8 @@ package org.springframework.util;
|
||||
*
|
||||
* Note that the default max elapsed time is {@link Long#MAX_VALUE}. Use
|
||||
* {@link #setMaxElapsedTime(long)} to limit the maximum number of time
|
||||
* that an instance should accumulate before returning {@link BackOff#STOP}.
|
||||
* that an instance should accumulate before returning
|
||||
* {@link BackOffExecution#STOP}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.1
|
||||
@@ -80,9 +81,6 @@ public class ExponentialBackOff implements BackOff {
|
||||
|
||||
private long maxElapsedTime = DEFAULT_MAX_ELAPSED_TIME;
|
||||
|
||||
private long currentInterval = -1;
|
||||
|
||||
private long currentElapsedTime = 0;
|
||||
|
||||
/**
|
||||
* Create an instance with the default settings.
|
||||
@@ -112,6 +110,13 @@ public class ExponentialBackOff implements BackOff {
|
||||
this.initialInterval = initialInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial interval in milliseconds.
|
||||
*/
|
||||
public long getInitialInterval() {
|
||||
return initialInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to multiply the current interval with for each retry attempt.
|
||||
*/
|
||||
@@ -120,6 +125,13 @@ public class ExponentialBackOff implements BackOff {
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value to multiply the current interval with for each retry attempt.
|
||||
*/
|
||||
public double getMultiplier() {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum back off time.
|
||||
*/
|
||||
@@ -127,50 +139,32 @@ public class ExponentialBackOff implements BackOff {
|
||||
this.maxInterval = maxInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum back off time.
|
||||
*/
|
||||
public long getMaxInterval() {
|
||||
return maxInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum elapsed time in milliseconds after which a call to
|
||||
* {@link #nextBackOff()} returns {@link BackOff#STOP}.
|
||||
* {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}.
|
||||
*/
|
||||
public void setMaxElapsedTime(long maxElapsedTime) {
|
||||
this.maxElapsedTime = maxElapsedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextBackOff() {
|
||||
if (currentElapsedTime >= maxElapsedTime) {
|
||||
return BackOff.STOP;
|
||||
}
|
||||
|
||||
long nextInterval = computeNextInterval();
|
||||
currentElapsedTime += nextInterval;
|
||||
return nextInterval;
|
||||
|
||||
/**
|
||||
* Return the maximum elapsed time in milliseconds after which a call to
|
||||
* {@link BackOffExecution#nextBackOff()} returns {@link BackOffExecution#STOP}.
|
||||
*/
|
||||
public long getMaxElapsedTime() {
|
||||
return maxElapsedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.currentInterval = -1;
|
||||
this.currentElapsedTime = 0;
|
||||
}
|
||||
|
||||
private long computeNextInterval() {
|
||||
if (this.currentInterval >= this.maxInterval) {
|
||||
return this.maxInterval;
|
||||
}
|
||||
else if (this.currentInterval < 0) {
|
||||
this.currentInterval = (this.initialInterval < this.maxInterval
|
||||
? this.initialInterval : this.maxInterval);
|
||||
}
|
||||
else {
|
||||
this.currentInterval = multiplyInterval();
|
||||
}
|
||||
return currentInterval;
|
||||
}
|
||||
|
||||
private long multiplyInterval() {
|
||||
long i = this.currentInterval;
|
||||
i *= this.multiplier;
|
||||
return (i > this.maxInterval ? this.maxInterval :i);
|
||||
public BackOffExecution start() {
|
||||
return new ExponentialBackOffExecution();
|
||||
}
|
||||
|
||||
private void checkMultiplier(double multiplier) {
|
||||
@@ -180,14 +174,56 @@ public class ExponentialBackOff implements BackOff {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String i = (this.currentInterval < 0 ? "n/a" : this.currentInterval + "ms");
|
||||
final StringBuilder sb = new StringBuilder("ExponentialBackOff{");
|
||||
sb.append("currentInterval=").append(i);
|
||||
sb.append(", multiplier=").append(this.multiplier);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
|
||||
private class ExponentialBackOffExecution implements BackOffExecution {
|
||||
|
||||
private long currentInterval = -1;
|
||||
|
||||
private long currentElapsedTime = 0;
|
||||
|
||||
@Override
|
||||
public long nextBackOff() {
|
||||
if (currentElapsedTime >= maxElapsedTime) {
|
||||
return BackOffExecution.STOP;
|
||||
}
|
||||
|
||||
long nextInterval = computeNextInterval();
|
||||
currentElapsedTime += nextInterval;
|
||||
return nextInterval;
|
||||
}
|
||||
|
||||
private long computeNextInterval() {
|
||||
long maxInterval = getMaxInterval();
|
||||
if (this.currentInterval >= maxInterval) {
|
||||
return maxInterval;
|
||||
}
|
||||
else if (this.currentInterval < 0) {
|
||||
long initialInterval = getInitialInterval();
|
||||
this.currentInterval = (initialInterval < maxInterval
|
||||
? initialInterval : maxInterval);
|
||||
}
|
||||
else {
|
||||
this.currentInterval = multiplyInterval(maxInterval);
|
||||
}
|
||||
return currentInterval;
|
||||
}
|
||||
|
||||
private long multiplyInterval(long maxInterval) {
|
||||
long i = this.currentInterval;
|
||||
i *= getMultiplier();
|
||||
return (i > maxInterval ? maxInterval : i);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String i = (this.currentInterval < 0 ? "n/a" : this.currentInterval + "ms");
|
||||
final StringBuilder sb = new StringBuilder("ExponentialBackOff{");
|
||||
sb.append("currentInterval=").append(i);
|
||||
sb.append(", multiplier=").append(getMultiplier());
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ public class FixedBackOff implements BackOff {
|
||||
|
||||
private long maxAttempts = UNLIMITED_ATTEMPTS;
|
||||
|
||||
private long currentAttempts = 0;
|
||||
|
||||
/**
|
||||
* Create an instance with an interval of {@value #DEFAULT_INTERVAL}
|
||||
@@ -87,31 +86,38 @@ public class FixedBackOff implements BackOff {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextBackOff() {
|
||||
this.currentAttempts++;
|
||||
if (this.currentAttempts <= this.maxAttempts) {
|
||||
return this.interval;
|
||||
}
|
||||
else {
|
||||
return BackOff.STOP;
|
||||
}
|
||||
public BackOffExecution start() {
|
||||
return new FixedBackOffExecution();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.currentAttempts = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("FixedBackOff{");
|
||||
sb.append("interval=").append(this.interval);
|
||||
String attemptValue = (this.maxAttempts == Long.MAX_VALUE ? "unlimited"
|
||||
: String.valueOf(this.maxAttempts));
|
||||
sb.append(", currentAttempts=").append(this.currentAttempts);
|
||||
sb.append(", maxAttempts=").append(attemptValue);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
private class FixedBackOffExecution implements BackOffExecution {
|
||||
|
||||
private long currentAttempts = 0;
|
||||
|
||||
@Override
|
||||
public long nextBackOff() {
|
||||
this.currentAttempts++;
|
||||
if (this.currentAttempts <= getMaxAttempts()) {
|
||||
return getInterval();
|
||||
}
|
||||
else {
|
||||
return BackOffExecution.STOP;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("FixedBackOff{");
|
||||
sb.append("interval=").append(FixedBackOff.this.interval);
|
||||
String attemptValue = (FixedBackOff.this.maxAttempts == Long.MAX_VALUE ? "unlimited"
|
||||
: String.valueOf(FixedBackOff.this.maxAttempts));
|
||||
sb.append(", currentAttempts=").append(this.currentAttempts);
|
||||
sb.append(", maxAttempts=").append(attemptValue);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user