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:
parent
49040a2925
commit
89fc3c0257
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,64 +34,73 @@ public class ExponentialBackOffTests {
|
|||
@Test
|
||||
public void defaultInstance() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff();
|
||||
assertEquals(2000l, backOff.nextBackOff());
|
||||
assertEquals(3000l, backOff.nextBackOff());
|
||||
assertEquals(4500l, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(2000l, execution.nextBackOff());
|
||||
assertEquals(3000l, execution.nextBackOff());
|
||||
assertEquals(4500l, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleIncrease() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff(100L, 2.0);
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
assertEquals(200l, backOff.nextBackOff());
|
||||
assertEquals(400l, backOff.nextBackOff());
|
||||
assertEquals(800l, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(100l, execution.nextBackOff());
|
||||
assertEquals(200l, execution.nextBackOff());
|
||||
assertEquals(400l, execution.nextBackOff());
|
||||
assertEquals(800l, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixedIncrease() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff(100L, 1.0);
|
||||
backOff.setMaxElapsedTime(300l);
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(100l, execution.nextBackOff());
|
||||
assertEquals(100l, execution.nextBackOff());
|
||||
assertEquals(100l, execution.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxIntervalReached() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff(2000L, 2.0);
|
||||
backOff.setMaxInterval(4000L);
|
||||
assertEquals(2000l, backOff.nextBackOff());
|
||||
assertEquals(4000l, backOff.nextBackOff());
|
||||
assertEquals(4000l, backOff.nextBackOff()); // max reached
|
||||
assertEquals(4000l, backOff.nextBackOff());
|
||||
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(2000l, execution.nextBackOff());
|
||||
assertEquals(4000l, execution.nextBackOff());
|
||||
assertEquals(4000l, execution.nextBackOff()); // max reached
|
||||
assertEquals(4000l, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAttemptsReached() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff(2000L, 2.0);
|
||||
backOff.setMaxElapsedTime(4000L);
|
||||
assertEquals(2000l, backOff.nextBackOff());
|
||||
assertEquals(4000l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff()); // > 4 sec wait in total
|
||||
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(2000l, execution.nextBackOff());
|
||||
assertEquals(4000l, execution.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff()); // > 4 sec wait in total
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetInstance() {
|
||||
public void startReturnDifferentInstances() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff();
|
||||
backOff.setInitialInterval(2000L);
|
||||
backOff.setMultiplier(2.0);
|
||||
backOff.setMaxElapsedTime(4000L);
|
||||
assertEquals(2000l, backOff.nextBackOff());
|
||||
assertEquals(4000l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
|
||||
backOff.reset();
|
||||
BackOffExecution execution = backOff.start();
|
||||
BackOffExecution execution2 = backOff.start();
|
||||
|
||||
assertEquals(2000l, backOff.nextBackOff());
|
||||
assertEquals(4000l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
assertEquals(2000l, execution.nextBackOff());
|
||||
assertEquals(2000l, execution2.nextBackOff());
|
||||
assertEquals(4000l, execution.nextBackOff());
|
||||
assertEquals(4000l, execution2.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution2.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -107,18 +116,20 @@ public class ExponentialBackOffTests {
|
|||
ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0);
|
||||
backOff.setMaxInterval(50L);
|
||||
|
||||
assertEquals(50L, backOff.nextBackOff());
|
||||
assertEquals(50L, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(50L, execution.nextBackOff());
|
||||
assertEquals(50L, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringContent() {
|
||||
ExponentialBackOff backOff = new ExponentialBackOff(2000L, 2.0);
|
||||
assertEquals("ExponentialBackOff{currentInterval=n/a, multiplier=2.0}", backOff.toString());
|
||||
backOff.nextBackOff();
|
||||
assertEquals("ExponentialBackOff{currentInterval=2000ms, multiplier=2.0}", backOff.toString());
|
||||
backOff.nextBackOff();
|
||||
assertEquals("ExponentialBackOff{currentInterval=4000ms, multiplier=2.0}", backOff.toString());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals("ExponentialBackOff{currentInterval=n/a, multiplier=2.0}", execution.toString());
|
||||
execution.nextBackOff();
|
||||
assertEquals("ExponentialBackOff{currentInterval=2000ms, multiplier=2.0}", execution.toString());
|
||||
execution.nextBackOff();
|
||||
assertEquals("ExponentialBackOff{currentInterval=4000ms, multiplier=2.0}", execution.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,57 +28,62 @@ public class FixedBackOffTests {
|
|||
@Test
|
||||
public void defaultInstance() {
|
||||
FixedBackOff backOff = new FixedBackOff();
|
||||
BackOffExecution execution = backOff.start();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
assertEquals(FixedBackOff.DEFAULT_INTERVAL, backOff.nextBackOff());
|
||||
assertEquals(FixedBackOff.DEFAULT_INTERVAL, execution.nextBackOff());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAttemptAtAll() {
|
||||
FixedBackOff backOff = new FixedBackOff(100L, 0L);
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAttemptsReached() {
|
||||
FixedBackOff backOff = new FixedBackOff(200L, 2);
|
||||
assertEquals(200l, backOff.nextBackOff());
|
||||
assertEquals(200l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(200l, execution.nextBackOff());
|
||||
assertEquals(200l, execution.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetOnInstance() {
|
||||
public void startReturnDifferentInstances() {
|
||||
FixedBackOff backOff = new FixedBackOff(100L, 1);
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
BackOffExecution execution2 = backOff.start();
|
||||
|
||||
backOff.reset();
|
||||
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
assertEquals(100l, execution.nextBackOff());
|
||||
assertEquals(100l, execution2.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution2.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void liveUpdate() {
|
||||
FixedBackOff backOff = new FixedBackOff(100L, 1);
|
||||
assertEquals(100l, backOff.nextBackOff());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals(100l, execution.nextBackOff());
|
||||
|
||||
backOff.setInterval(200l);
|
||||
backOff.setMaxAttempts(2);
|
||||
|
||||
assertEquals(200l, backOff.nextBackOff());
|
||||
assertEquals(BackOff.STOP, backOff.nextBackOff());
|
||||
assertEquals(200l, execution.nextBackOff());
|
||||
assertEquals(BackOffExecution.STOP, execution.nextBackOff());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringContent() {
|
||||
FixedBackOff backOff = new FixedBackOff(200L, 10);
|
||||
assertEquals("FixedBackOff{interval=200, currentAttempts=0, maxAttempts=10}", backOff.toString());
|
||||
backOff.nextBackOff();
|
||||
assertEquals("FixedBackOff{interval=200, currentAttempts=1, maxAttempts=10}", backOff.toString());
|
||||
backOff.nextBackOff();
|
||||
assertEquals("FixedBackOff{interval=200, currentAttempts=2, maxAttempts=10}", backOff.toString());
|
||||
BackOffExecution execution = backOff.start();
|
||||
assertEquals("FixedBackOff{interval=200, currentAttempts=0, maxAttempts=10}", execution.toString());
|
||||
execution.nextBackOff();
|
||||
assertEquals("FixedBackOff{interval=200, currentAttempts=1, maxAttempts=10}", execution.toString());
|
||||
execution.nextBackOff();
|
||||
assertEquals("FixedBackOff{interval=200, currentAttempts=2, maxAttempts=10}", execution.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import org.springframework.scheduling.SchedulingAwareRunnable;
|
|||
import org.springframework.scheduling.SchedulingTaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.BackOff;
|
||||
import org.springframework.util.BackOffExecution;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.FixedBackOff;
|
||||
|
||||
|
|
@ -221,8 +222,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
|||
|
||||
/**
|
||||
* Specify the {@link BackOff} instance to use to compute the interval
|
||||
* between recovery attempts. If the {@link BackOff} implementation
|
||||
* returns {@link BackOff#STOP}, this listener container will not further
|
||||
* between recovery attempts. If the {@link BackOffExecution} implementation
|
||||
* returns {@link BackOffExecution#STOP}, this listener container will not further
|
||||
* attempt to recover.
|
||||
* <p>The {@link #setRecoveryInterval(long) recovery interval} is ignored
|
||||
* when this property is set.
|
||||
|
|
@ -897,6 +898,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
|||
* @see #stop()
|
||||
*/
|
||||
protected void refreshConnectionUntilSuccessful() {
|
||||
BackOffExecution execution = backOff.start();
|
||||
while (isRunning()) {
|
||||
try {
|
||||
if (sharedConnectionEnabled()) {
|
||||
|
|
@ -907,7 +909,6 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
|||
JmsUtils.closeConnection(con);
|
||||
}
|
||||
logger.info("Successfully refreshed JMS Connection");
|
||||
backOff.reset();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
|
@ -917,7 +918,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
|||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Could not refresh JMS Connection for destination '");
|
||||
msg.append(getDestinationDescription()).append("' - retrying using ");
|
||||
msg.append(this.backOff).append(". Cause: ");
|
||||
msg.append(execution).append(". Cause: ");
|
||||
msg.append(ex instanceof JMSException ? JmsUtils.buildExceptionMessage((JMSException) ex) : ex.getMessage());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.error(msg, ex);
|
||||
|
|
@ -926,7 +927,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
|||
logger.error(msg);
|
||||
}
|
||||
}
|
||||
if (!applyBackOffTime()) {
|
||||
if (!applyBackOffTime(execution)) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
|
@ -952,13 +953,14 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
|||
}
|
||||
|
||||
/**
|
||||
* Apply the next back off time. Return {@code true} if the back off period has
|
||||
* been applied and a new attempt to recover should be made, {@code false} if no
|
||||
* further attempt should be made.
|
||||
* Apply the next back off time using the specified {@link BackOffExecution}.
|
||||
* <p>Return {@code true} if the back off period has been applied and a new
|
||||
* attempt to recover should be made, {@code false} if no further attempt
|
||||
* should be made.
|
||||
*/
|
||||
protected boolean applyBackOffTime() {
|
||||
long interval = backOff.nextBackOff();
|
||||
if (interval == BackOff.STOP) {
|
||||
protected boolean applyBackOffTime(BackOffExecution execution) {
|
||||
long interval = execution.nextBackOff();
|
||||
if (interval == BackOffExecution.STOP) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@
|
|||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specify the BackOff instance to use to compute the interval between recovery
|
||||
attempts. If the BackOff implementation returns "BackOff#STOP", the listener
|
||||
attempts. If the BackOff implementation returns "BackOffExecution#STOP", the listener
|
||||
container will not further attempt to recover. The recovery-interval value is
|
||||
ignored when this property is set. The default is a FixedBackOff with an
|
||||
interval of 5000 ms, that is 5 seconds.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.mockito.invocation.InvocationOnMock;
|
|||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.util.BackOff;
|
||||
import org.springframework.util.BackOffExecution;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -39,7 +40,9 @@ public class DefaultMessageListenerContainerTests {
|
|||
@Test
|
||||
public void applyBackOff() {
|
||||
BackOff mock = mock(BackOff.class);
|
||||
given(mock.nextBackOff()).willReturn(BackOff.STOP);
|
||||
BackOffExecution execution = mock(BackOffExecution.class);
|
||||
given(execution.nextBackOff()).willReturn(BackOffExecution.STOP);
|
||||
given(mock.start()).willReturn(execution);
|
||||
|
||||
DefaultMessageListenerContainer container = createContainer(mock, createFailingContainerFactory());
|
||||
container.start();
|
||||
|
|
@ -48,34 +51,40 @@ public class DefaultMessageListenerContainerTests {
|
|||
container.refreshConnectionUntilSuccessful();
|
||||
|
||||
assertEquals(false, container.isRunning());
|
||||
verify(mock).nextBackOff();
|
||||
verify(mock).start();
|
||||
verify(execution).nextBackOff();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyBackOffRetry() {
|
||||
BackOff mock = mock(BackOff.class);
|
||||
given(mock.nextBackOff()).willReturn(50L, BackOff.STOP);
|
||||
BackOffExecution execution = mock(BackOffExecution.class);
|
||||
given(execution.nextBackOff()).willReturn(50L, BackOffExecution.STOP);
|
||||
given(mock.start()).willReturn(execution);
|
||||
|
||||
DefaultMessageListenerContainer container = createContainer(mock, createFailingContainerFactory());
|
||||
container.start();
|
||||
container.refreshConnectionUntilSuccessful();
|
||||
|
||||
assertEquals(false, container.isRunning());
|
||||
verify(mock, times(2)).nextBackOff();
|
||||
verify(mock).start();
|
||||
verify(execution, times(2)).nextBackOff();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recoverResetBackOff() {
|
||||
BackOff mock = mock(BackOff.class);
|
||||
given(mock.nextBackOff()).willReturn(50L, 50L, 50L); // 3 attempts max
|
||||
BackOffExecution execution = mock(BackOffExecution.class);
|
||||
given(execution.nextBackOff()).willReturn(50L, 50L, 50L); // 3 attempts max
|
||||
given(mock.start()).willReturn(execution);
|
||||
|
||||
DefaultMessageListenerContainer container = createContainer(mock, createRecoverableContainerFactory(1));
|
||||
container.start();
|
||||
container.refreshConnectionUntilSuccessful();
|
||||
|
||||
assertEquals(true, container.isRunning());
|
||||
verify(mock, times(1)).nextBackOff(); // only on attempt as the second one lead to a recovery
|
||||
verify(mock, times(1)).reset(); // reset should have been called
|
||||
verify(mock).start();
|
||||
verify(execution, times(1)).nextBackOff(); // only on attempt as the second one lead to a recovery
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
|||
|
|
@ -41433,10 +41433,10 @@ choices and message redelivery scenarios.
|
|||
|
||||
| back-off
|
||||
| Specify the `BackOff` instance to use to compute the interval between recovery
|
||||
attempts. If the `BackOff` implementation returns `BackOff#STOP`, the listener
|
||||
container will not further attempt to recover. The `recovery-interval value is
|
||||
is ignored when this property is set. The default is a `FixedBackOff` with an
|
||||
interval of 5000 ms, that is 5 seconds.
|
||||
attempts. If the `BackOffExecution` implementation returns `BackOffExecution#STOP`,
|
||||
the listener container will not further attempt to recover. The `recovery-interval
|
||||
value is ignored when this property is set. The default is a `FixedBackOff` with
|
||||
an interval of 5000 ms, that is 5 seconds.
|
||||
|
||||
| recovery-interval
|
||||
| Specify the interval between recovery attempts, in milliseconds. Convenience
|
||||
|
|
|
|||
Loading…
Reference in New Issue