diff --git a/spring-core/src/main/java/org/springframework/util/BackOff.java b/spring-core/src/main/java/org/springframework/util/BackOff.java index 605cfd43c47..01c43932ae6 100644 --- a/spring-core/src/main/java/org/springframework/util/BackOff.java +++ b/spring-core/src/main/java/org/springframework/util/BackOff.java @@ -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. * *
Users of this interface are expected to use it like this: * *
* {@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;
* }
* }
*
- * Once the underlying operation has completed successfully, the instance
- * must 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();
}
diff --git a/spring-core/src/main/java/org/springframework/util/BackOffExecution.java b/spring-core/src/main/java/org/springframework/util/BackOffExecution.java
new file mode 100644
index 00000000000..d5a45fa556a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/BackOffExecution.java
@@ -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.
+ *
+ * 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(); + +} diff --git a/spring-core/src/main/java/org/springframework/util/ExponentialBackOff.java b/spring-core/src/main/java/org/springframework/util/ExponentialBackOff.java index 1031f04aaa9..3e0f533eda6 100644 --- a/spring-core/src/main/java/org/springframework/util/ExponentialBackOff.java +++ b/spring-core/src/main/java/org/springframework/util/ExponentialBackOff.java @@ -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(); + } } } diff --git a/spring-core/src/main/java/org/springframework/util/FixedBackOff.java b/spring-core/src/main/java/org/springframework/util/FixedBackOff.java index 806baeb0eee..c32f12b7b32 100644 --- a/spring-core/src/main/java/org/springframework/util/FixedBackOff.java +++ b/spring-core/src/main/java/org/springframework/util/FixedBackOff.java @@ -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(); + } + } } diff --git a/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java b/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java index ed1fc197ec6..6fbfc8fde7e 100644 --- a/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java +++ b/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java @@ -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()); } } diff --git a/spring-core/src/test/java/org/springframework/util/FixedBackOffTests.java b/spring-core/src/test/java/org/springframework/util/FixedBackOffTests.java index 3c469d31acd..3359fc925b7 100644 --- a/spring-core/src/test/java/org/springframework/util/FixedBackOffTests.java +++ b/spring-core/src/test/java/org/springframework/util/FixedBackOffTests.java @@ -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()); } } diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java index f724e593b55..6d02aedddfe 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java @@ -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. *
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}. + *
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 {
diff --git a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.1.xsd b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.1.xsd
index 1987af77c4e..38262d44bc6 100644
--- a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.1.xsd
+++ b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.1.xsd
@@ -322,7 +322,7 @@