Permalink
Browse files

Added RetrySimulator - simulate retry + backoff loops

  - The RetrySimulator can be used to calibrate
    retry + backoff tuples.
  • Loading branch information...
trav authored and dsyer committed May 29, 2012
1 parent 048e3de commit 9eebfe2288b19d15f01e6aad6c91724675236da0
@@ -39,7 +39,7 @@
* @author Dave Syer
* @author Gary Russell
*/
-public class ExponentialBackOffPolicy implements BackOffPolicy {
+public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<ExponentialBackOffPolicy> {
protected final Log logger = LogFactory.getLog(this.getClass());
/**
@@ -84,6 +84,24 @@ public void setSleeper(Sleeper sleeper) {
this.sleeper = sleeper;
}
+ public ExponentialBackOffPolicy withSleeper(Sleeper sleeper) {
+ ExponentialBackOffPolicy res = newInstance();
+ cloneValues(res);
+ res.setSleeper(sleeper);
+ return res;
+ }
+
+ protected ExponentialBackOffPolicy newInstance() {
+ return new ExponentialBackOffPolicy();
+ }
+
+ protected void cloneValues(ExponentialBackOffPolicy target) {
+ target.setInitialInterval(getInitialInterval());
+ target.setMaxInterval(getMaxInterval());
+ target.setMultiplier(getMultiplier());
+ target.setSleeper(sleeper);
+ }
+
/**
* Set the initial sleep interval value. Default is <code>100</code>
* millisecond. Cannot be set to a value less than one.
@@ -27,7 +27,7 @@
* @author Rob Harrop
* @author Dave Syer
*/
-public class FixedBackOffPolicy extends StatelessBackOffPolicy {
+public class FixedBackOffPolicy extends StatelessBackOffPolicy implements SleepingBackOffPolicy<FixedBackOffPolicy> {
/**
* Default back off period - 1000ms.
@@ -41,7 +41,14 @@
private Sleeper sleeper = new ObjectWaitSleeper();
-
+
+ public FixedBackOffPolicy withSleeper(Sleeper sleeper) {
+ FixedBackOffPolicy res = new FixedBackOffPolicy();
+ res.setBackOffPeriod(backOffPeriod);
+ res.setSleeper(sleeper);
+ return res;
+ }
+
/**
* Public setter for the {@link Sleeper} strategy.
* @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}.
@@ -78,4 +85,8 @@ protected void doBackOff() throws BackOffInterruptedException {
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
}
}
+
+ public String toString() {
+ return "FixedBackOffPolicy[backOffPeriod=" + backOffPeriod + "]";
+ }
}
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * A interface which can be mixed in by {@link BackOffPolicy}s indicating that they sleep
+ * when backing off.
+ */
+public interface SleepingBackOffPolicy<T extends SleepingBackOffPolicy> extends BackOffPolicy {
+ /**
+ * Clone the policy and return a new policy which uses the passed sleeper.
+ *
+ * @param sleeper Target to be invoked any time the backoff policy sleeps
+ * @return a clone of this policy which will have all of its backoff sleeps
+ * routed into the passed sleeper
+ */
+ T withSleeper(Sleeper sleeper);
+}
@@ -23,6 +23,7 @@
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.context.RetryContextSupport;
+import org.springframework.util.ClassUtils;
/**
*
@@ -150,4 +151,8 @@ public SimpleRetryContext(RetryContext parent) {
private boolean retryForException(Throwable ex) {
return retryableClassifier.classify(ex);
}
+
+ public String toString() {
+ return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + maxAttempts + "]";
+ }
}
@@ -0,0 +1,114 @@
+/*
+ * 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.support;
+
+
+import java.util.*;
+
+/**
+ * The results of a simulation.
+ */
+public class RetrySimulation {
+ private final List<SleepSequence> sleepSequences = new ArrayList<SleepSequence>();
+ private final Map<Long, Long> sleepHistogram = new HashMap<Long, Long>();
+
+ /**
+ * Add a sequence of sleeps to the simulation.
+ */
+ public void addSequence(List<Long> sleeps) {
+ for (Long sleep : sleeps) {
+ Long existingHisto = sleepHistogram.get(sleep);
+ if (existingHisto == null) {
+ sleepHistogram.put(sleep, 1l);
+ } else {
+ sleepHistogram.put(sleep, existingHisto + 1);
+ }
+ }
+
+ sleepSequences.add(new SleepSequence(sleeps));
+ }
+
+ /**
+ * @return Returns a list of all the unique sleep values which were executed within
+ * all simulations.
+ */
+ public List<Long> getUniqueSleeps() {
+ List<Long> res = new ArrayList<Long>(sleepHistogram.keySet());
+ Collections.sort(res);
+ return res;
+ }
+
+ /**
+ * @return the count of each sleep which was seen throughout all sleeps.
+ * histogram[i] = sum(getUniqueSleeps()[i])
+ */
+ public List<Long> getUniqueSleepsHistogram() {
+ List<Long> res = new ArrayList<Long>(sleepHistogram.size());
+ for (Long sleep : getUniqueSleeps()) {
+ res.add(sleepHistogram.get(sleep));
+ }
+ return res;
+ }
+
+ /**
+ * @return the longest total time slept by a retry sequence.
+ */
+ public SleepSequence getLongestTotalSleepSequence() {
+ SleepSequence longest = null;
+ for (SleepSequence sequence : sleepSequences) {
+ if (longest == null || sequence.getTotalSleep() > longest.getTotalSleep()) {
+ longest = sequence;
+ }
+ }
+ return longest;
+ }
+
+ public static class SleepSequence {
+ private final List<Long> sleeps;
+ private final long longestSleep;
+ private final long totalSleep;
+
+ public SleepSequence(List<Long> sleeps) {
+ this.sleeps = sleeps;
+ this.longestSleep = Collections.max(sleeps);
+ long totalSleep = 0;
+ for (Long sleep : sleeps) {
+ totalSleep += sleep;
+ }
+ this.totalSleep = totalSleep;
+ }
+
+ public List<Long> getSleeps() {
+ return sleeps;
+ }
+
+ /**
+ * Returns the longest individual sleep within this sequence.
+ */
+ public long getLongestSleep() {
+ return longestSleep;
+ }
+
+ public long getTotalSleep() {
+ return totalSleep;
+ }
+
+ public String toString() {
+ return "totalSleep=" + totalSleep + ": " + sleeps.toString();
+ }
+ }
+}
@@ -0,0 +1,118 @@
+/*
+ * 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.support;
+
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryPolicy;
+import org.springframework.retry.backoff.Sleeper;
+import org.springframework.retry.backoff.SleepingBackOffPolicy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link RetrySimulator} is a tool for exercising retry + backoff operations.
+ *
+ * When calibrating a set of retry + backoff pairs, it is useful to know the behaviour
+ * of the retry for various scenarios.
+ *
+ * Things you may want to know:
+ * - Does a 'maxInterval' of 5000 ms in my backoff even matter?
+ * (This is often the case when retry counts are low -- so why set the max interval
+ * at something that cannot be achieved?)
+ * - What are the typical sleep durations for threads in a retry
+ * - What was the longest sleep duration for any retry sequence
+ *
+ * The simulator provides this information by executing a retry + backoff pair until failure
+ * (that is all retries are exhausted). The information about each retry is provided
+ * as part of the {@link RetrySimulation}.
+ *
+ * Note that the impetus for this class was to expose the timings which are possible with
+ * {@link org.springframework.retry.backoff.ExponentialRandomBackOffPolicy}, which provides
+ * random values and must be looked at over a series of trials.
+ *
+ * @author Jon Travis
+ */
+public class RetrySimulator {
+ private final SleepingBackOffPolicy backOffPolicy;
+ private final RetryPolicy retryPolicy;
+
+ public RetrySimulator(SleepingBackOffPolicy backOffPolicy, RetryPolicy retryPolicy) {
+ this.backOffPolicy = backOffPolicy;
+ this.retryPolicy = retryPolicy;
+ }
+
+ /**
+ * Execute the simulator for a give # of iterations.
+ *
+ * @param numSimulations Number of simulations to run
+ * @return the outcome of all simulations
+ */
+ public RetrySimulation executeSimulation(int numSimulations) {
+ RetrySimulation simulation = new RetrySimulation();
+
+ for (int i=0; i<numSimulations; i++) {
+ simulation.addSequence(executeSingleSimulation());
+ }
+ return simulation;
+ }
+
+ /**
+ * Execute a single simulation
+ * @return The sleeps which occurred within the single simulation.
+ */
+ public List<Long> executeSingleSimulation() {
+ StealingSleeper stealingSleeper = new StealingSleeper();
+ SleepingBackOffPolicy stealingBackoff = backOffPolicy.withSleeper(stealingSleeper);
+
+ RetryTemplate template = new RetryTemplate();
+ template.setBackOffPolicy(stealingBackoff);
+ template.setRetryPolicy(retryPolicy);
+
+ try {
+ template.execute(new FailingRetryCallback<Object>());
+ } catch(FailingRetryException e) {
+
+ } catch(Exception e) {
+ throw new RuntimeException("Unexpected exception", e);
+ }
+
+ return stealingSleeper.getSleeps();
+ }
+
+ static class FailingRetryCallback<Object> implements RetryCallback<Object> {
+ public Object doWithRetry(RetryContext context) throws Exception {
+ throw new FailingRetryException();
+ }
+ }
+
+ static class FailingRetryException extends Exception {
+ }
+
+ static class StealingSleeper implements Sleeper {
+ private final List<Long> sleeps = new ArrayList<Long>();
+
+ public void sleep(long backOffPeriod) throws InterruptedException {
+ sleeps.add(backOffPeriod);
+ }
+
+ public List<Long> getSleeps() {
+ return sleeps;
+ }
+ }
+}
Oops, something went wrong.

0 comments on commit 9eebfe2

Please sign in to comment.