Skip to content

Commit

Permalink
AMQP-226 Fix Exponential Back Off
Browse files Browse the repository at this point in the history
Previously, for stateful environments,the
multiplier had no effect and the backoff
policy always slept for the initial interval.

AMQP-226 Polishing

Using AttributeAccessor interface instead of concrete
RetryContextSupport.
  • Loading branch information
garyrussell authored and olegz committed May 10, 2012
1 parent c36d1f6 commit c53f1f0
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* 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.
Expand All @@ -16,6 +16,8 @@

package org.springframework.retry.backoff;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.retry.RetryContext;
import org.springframework.util.ClassUtils;

Expand All @@ -35,9 +37,11 @@
*
* @author Rob Harrop
* @author Dave Syer
* @author Gary Russell
*/
public class ExponentialBackOffPolicy implements BackOffPolicy {

protected final Log logger = LogFactory.getLog(this.getClass());
/**
* The default 'initialInterval' value - 100 millisecs. Coupled with the
* default 'multiplier' value this gives a useful initial spread of pauses
Expand Down Expand Up @@ -151,7 +155,11 @@ public BackOffContext start(RetryContext context) {
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
ExponentialBackOffContext context = (ExponentialBackOffContext) backOffContext;
try {
sleeper.sleep(context.getSleepAndIncrement());
long sleepTime = context.getSleepAndIncrement();
if (logger.isDebugEnabled()) {
logger.debug("Sleeping for " + sleepTime);
}
sleeper.sleep(sleepTime);
}
catch (InterruptedException e) {
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
Expand Down
22 changes: 19 additions & 3 deletions src/main/java/org/springframework/retry/support/RetryTemplate.java
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* 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.
Expand All @@ -23,6 +23,7 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.AttributeAccessor;
import org.springframework.retry.ExhaustedRetryException;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
Expand Down Expand Up @@ -67,6 +68,7 @@
*
* @author Rob Harrop
* @author Dave Syer
* @author Gary Russell
*/
public class RetryTemplate implements RetryOperations {

Expand Down Expand Up @@ -220,8 +222,22 @@ protected <T> T doExecute(RetryCallback<T> retryCallback, RecoveryCallback<T> re
throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
}

// Start the backoff context...
BackOffContext backOffContext = backOffPolicy.start(context);
// Get or Start the backoff context...
BackOffContext backOffContext = null;
AttributeAccessor attributeAccessor = null;
if (context instanceof AttributeAccessor) {
attributeAccessor = (AttributeAccessor) context;
Object resource = attributeAccessor.getAttribute("backOffContext");
if (resource instanceof BackOffContext) {
backOffContext = (BackOffContext) resource;
}
}
if (backOffContext == null) {
backOffContext = backOffPolicy.start(context);
if (attributeAccessor != null && backOffContext != null) {
attributeAccessor.setAttribute("backOffContext", backOffContext);
}
}

/*
* We allow the whole loop to be skipped if the policy or context
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* 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.
Expand All @@ -22,18 +22,23 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Test;
import org.springframework.retry.ExhaustedRetryException;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryState;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.DefaultRetryState;
import org.springframework.retry.support.RetryTemplate;

/**
* @author Dave Syer
* @author Gary Russell
*
*/
public class StatefulRetryIntegrationTests {
Expand Down Expand Up @@ -114,6 +119,38 @@ public void testExternalRetryWithSuccessOnRetry() throws Exception {
assertEquals("bar", result);
}

@Test
public void testExponentialBackOffIsExponential() throws Exception {
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
policy.setInitialInterval(100);
policy.setMultiplier(1.5);
RetryTemplate template = new RetryTemplate();
template.setBackOffPolicy(policy);
final List<Long> times = new ArrayList<Long>();
RetryState retryState = new DefaultRetryState("bar");
for (int i = 0; i < 3; i++) {
try {
template.execute(new RetryCallback<String>() {
public String doWithRetry(RetryContext context) throws Exception {
times.add(System.currentTimeMillis());
throw new Exception("Fail");
}
}, new RecoveryCallback<String>() {
public String recover(RetryContext context)
throws Exception {
return null;
}
}, retryState);
}
catch (Exception e) {
assertTrue(e.getMessage().equals("Fail"));
}
}
assertEquals(3, times.size());
assertTrue(times.get(1) - times.get(0) >= 100);
assertTrue(times.get(2) - times.get(1) >= 150);
}

/**
* @author Dave Syer
*
Expand Down

0 comments on commit c53f1f0

Please sign in to comment.