Permalink
Browse files

Add the ExponentialRandomBackOffPolicy

	- Gives better performance when run in a very
    contentious environment.  Specifically when
    running multi-threaded tests where all threads
	may start at the same time.
  • Loading branch information...
1 parent 9eebfe2 commit 759d40ed5d79e2d83c537d403a5332951512c983 @trav trav committed with dsyer May 29, 2012
@@ -184,7 +184,7 @@ public void backOff(BackOffContext backOffContext) throws BackOffInterruptedExce
}
}
- private static class ExponentialBackOffContext implements BackOffContext {
+ static class ExponentialBackOffContext implements BackOffContext {
private final double multiplier;
@@ -198,16 +198,32 @@ public ExponentialBackOffContext(long expSeed, double multiplier, long maxInterv
this.maxInterval = maxInterval;
}
- public synchronized long getSleepAndIncrement() {
- long sleep = this.interval;
- if (sleep > maxInterval) {
- sleep = (long) maxInterval;
- }
- else {
- this.interval *= this.multiplier;
- }
- return sleep;
- }
+ public synchronized long getSleepAndIncrement() {
+ long sleep = this.interval;
+ if (sleep > maxInterval) {
+ sleep = (long) maxInterval;
+ }
+ else {
+ this.interval = getNextInterval();
+ }
+ return sleep;
+ }
+
+ protected long getNextInterval() {
+ return (long)(this.interval * this.multiplier);
+ }
+
+ public double getMultiplier() {
+ return multiplier;
+ }
+
+ public long getInterval() {
+ return interval;
+ }
+
+ public long getMaxInterval() {
+ return maxInterval;
+ }
}
public String toString() {
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2006-2012 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.retry.backoff;
+
+import org.springframework.retry.RetryContext;
+
+import java.util.Random;
+
+/**
+ * Implementation of {@link org.springframework.retry.backoff.ExponentialBackOffPolicy} that
+ * chooses a random multiple of the interval. The random multiple is selected based on
+ * how many iterations have occurred.
+ *
+ * This has shown to at least be useful in testing scenarios where excessive contention is generated
+ * by the test needing many retries. In test, usually threads are started at the same time, and thus
+ * stomp together onto the next interval. Using this {@link BackOffPolicy} can help avoid that scenario.
+ *
+ * Example:
+ * initialInterval = 50
+ * multiplier = 2.0
+ * maxInterval = 3000
+ * numRetries = 5
+ *
+ * {@link ExponentialBackOffPolicy} yields: [50, 100, 200, 400, 800]
+ *
+ * {@link ExponentialRandomBackOffPolicy} may yield [50, 100, 100, 100, 600]
+ * or [50, 100, 150, 400, 800]
+ * @author Jon Travis
+ */
+public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
+ /**
+ * Returns a new instance of {@link org.springframework.retry.backoff.BackOffContext}, seeded with this
+ * policies settings.
+ */
+ public BackOffContext start(RetryContext context) {
+ return new ExponentialRandomBackOffContext(getInitialInterval(), getMultiplier(), getMaxInterval());
+ }
+
+ protected ExponentialBackOffPolicy newInstance() {
+ return new ExponentialRandomBackOffPolicy();
+ }
+
+ static class ExponentialRandomBackOffContext extends ExponentialBackOffPolicy.ExponentialBackOffContext {
+ private final Random r = new Random();
+ private final long initialInterval;
+ private long intervalIdx;
+
+ public ExponentialRandomBackOffContext(long expSeed, double multiplier, long maxInterval) {
+ super(expSeed, multiplier, maxInterval);
+ this.initialInterval = expSeed;
+ this.intervalIdx = 0;
+ }
+
+ @Override
+ protected synchronized long getNextInterval() {
+ intervalIdx++;
+ return initialInterval + initialInterval * Math.max(1, r.nextInt((int)Math.pow(getMultiplier(), intervalIdx)));
+ }
+ }
+}
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2006-2007 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.retry.backoff;
+
+import junit.framework.TestCase;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetrySimulation;
+import org.springframework.retry.support.RetrySimulator;
+
+import java.util.*;
+
+public class ExponentialRandomBackOffPolicyTests extends TestCase {
+ static final int NUM_TRIALS = 10000;
+ static final int MAX_RETRIES = 6;
+
+ private ExponentialBackOffPolicy makeBackoffPolicy() {
+ ExponentialBackOffPolicy policy = new ExponentialRandomBackOffPolicy();
+ policy.setInitialInterval(50);
+ policy.setMultiplier(2.0);
+ policy.setMaxInterval(3000);
+ return policy;
+ }
+
+ private SimpleRetryPolicy makeRetryPolicy() {
+ SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
+ retryPolicy.setMaxAttempts(MAX_RETRIES);
+ return retryPolicy;
+ }
+
+ public void testSingleBackoff() throws Exception {
+ ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
+ RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
+ RetrySimulation simulation = simulator.executeSimulation(1);
+
+ List<Long> sleeps = simulation.getLongestTotalSleepSequence().getSleeps();
+ System.out.println("Single trial of " + backOffPolicy + ": sleeps=" + sleeps);
+ assertEquals(MAX_RETRIES - 1, sleeps.size());
+ long initialInterval = backOffPolicy.getInitialInterval();
+ for (int i=0; i<sleeps.size(); i++) {
+ assertTrue(sleeps.get(i) % backOffPolicy.getInitialInterval() == 0);
+
+ long expectedMaxValue = (long) (initialInterval + initialInterval * Math.max(1, Math.pow(backOffPolicy.getMultiplier(), i)));
+ assertTrue("Found a sleep [" + sleeps.get(i) + "] which exceeds our max expected value of " + expectedMaxValue + " at interval " + i,
+ sleeps.get(i) < expectedMaxValue);
+ }
+ }
+
+ public void testMultiBackOff() throws Exception {
+ ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
+ RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
+ RetrySimulation simulation = simulator.executeSimulation(NUM_TRIALS);
+
+ System.out.println("Ran " + NUM_TRIALS + " backoff trials. Each trial retried " + MAX_RETRIES + " times");
+ System.out.println("Policy: " + backOffPolicy);
+ System.out.println("All generated backoffs:");
+ System.out.println(" " + simulation.getUniqueSleeps());
+
+ System.out.println("Backoff frequencies:");
+ System.out.print(" " + simulation.getUniqueSleepsHistogram());
+
+ long expectedSleep = backOffPolicy.getInitialInterval();
+ List<Long> allSleeps = simulation.getUniqueSleeps();
+ for (int j=0; j<allSleeps.size(); j++) {
+ long sleep = allSleeps.get(j);
+ assertEquals("Missing a multiple of " + backOffPolicy.getInitialInterval(), sleep, expectedSleep);
+
+ expectedSleep += backOffPolicy.getInitialInterval();
+ }
+ }
+}

0 comments on commit 759d40e

Please sign in to comment.