diff --git a/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java index 7db23d8..21d0ffc 100644 --- a/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java +++ b/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java @@ -184,7 +184,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy 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() { diff --git a/src/main/java/org/springframework/retry/backoff/ExponentialRandomBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/ExponentialRandomBackOffPolicy.java new file mode 100644 index 0000000..ed66e9f --- /dev/null +++ b/src/main/java/org/springframework/retry/backoff/ExponentialRandomBackOffPolicy.java @@ -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))); + } + } +} diff --git a/src/test/java/org/springframework/retry/backoff/ExponentialRandomBackOffPolicyTests.java b/src/test/java/org/springframework/retry/backoff/ExponentialRandomBackOffPolicyTests.java new file mode 100644 index 0000000..aeacb02 --- /dev/null +++ b/src/test/java/org/springframework/retry/backoff/ExponentialRandomBackOffPolicyTests.java @@ -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 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 allSleeps = simulation.getUniqueSleeps(); + for (int j=0; j