RESOLVED - issue BATCH-537: Bad ItemKeyGenerator strategy can lead to infinite loop in retry

Some changes to account for retry exceptions and force a failure if strange hashCode/equals are detected.
This commit is contained in:
dsyer
2008-04-01 20:09:51 +00:00
parent 0ea4e286e8
commit b050f95f03
10 changed files with 316 additions and 44 deletions

View File

@@ -22,6 +22,7 @@ import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.listener.RetryListenerSupport;
import org.springframework.batch.support.BinaryExceptionClassifier;
/**
* @author Dave Syer
@@ -34,17 +35,22 @@ public class SimpleRetryExceptionHandler extends RetryListenerSupport implements
*/
private static final String EXHAUSTED = SimpleRetryExceptionHandler.class.getName() + ".RETRY_EXHAUSTED";
private RetryPolicy retryPolicy;
final private RetryPolicy retryPolicy;
private ExceptionHandler exceptionHandler;
final private ExceptionHandler exceptionHandler;
final private BinaryExceptionClassifier fatalExceptionClassifier;
/**
* @param retryPolicy
* @param exceptionHandler
* @param classes
*/
public SimpleRetryExceptionHandler(RetryPolicy retryPolicy, ExceptionHandler exceptionHandler) {
public SimpleRetryExceptionHandler(RetryPolicy retryPolicy, ExceptionHandler exceptionHandler, Class[] classes) {
this.retryPolicy = retryPolicy;
this.exceptionHandler = exceptionHandler;
this.fatalExceptionClassifier = new BinaryExceptionClassifier();
fatalExceptionClassifier.setExceptionClasses(classes);
}
/*
@@ -55,7 +61,7 @@ public class SimpleRetryExceptionHandler extends RetryListenerSupport implements
public void handleException(RepeatContext context, Throwable throwable) throws Throwable {
// Only bother to check the delegate exception handler if we know that
// retry is exhausted
if (context.hasAttribute(EXHAUSTED)) {
if (!fatalExceptionClassifier.isDefault(throwable) || context.hasAttribute(EXHAUSTED)) {
exceptionHandler.handleException(context, throwable);
}
}

View File

@@ -111,11 +111,8 @@ public class SkipLimitStepFactoryBean extends SimpleStepFactoryBean {
* to absorb exceptions at the step level because the failed items
* will never re-appear after a rollback.
*/
List fatalExceptionList = new ArrayList(Arrays.asList(fatalExceptionClasses));
if (!fatalExceptionList.contains(SkipLimitExceededException.class)) {
fatalExceptionList.add(SkipLimitExceededException.class);
}
fatalExceptionClasses = (Class[]) fatalExceptionList.toArray(new Class[0]);
addFatalExceptionIfMissing(SkipLimitExceededException.class);
List fatalExceptionList = Arrays.asList(fatalExceptionClasses);
LimitCheckingItemSkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, Arrays
.asList(skippableExceptionClasses), fatalExceptionList);
@@ -143,4 +140,15 @@ public class SkipLimitStepFactoryBean extends SimpleStepFactoryBean {
step.setItemHandler(itemHandler);
}
/**
* @return
*/
public void addFatalExceptionIfMissing(Class cls) {
List fatalExceptionList = new ArrayList(Arrays.asList(fatalExceptionClasses));
if (!fatalExceptionList.contains(cls)) {
fatalExceptionList.add(cls);
}
fatalExceptionClasses = (Class[]) fatalExceptionList.toArray(new Class[0]);
}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.batch.item.ItemKeyGenerator;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.retry.RetryException;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryPolicy;
@@ -116,6 +117,8 @@ public class StatefulRetryStepFactoryBean extends SkipLimitStepFactoryBean {
if (retryLimit > 0) {
addFatalExceptionIfMissing(RetryException.class);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(retryLimit);
if (retryableExceptionClasses != null) {
retryPolicy.setRetryableExceptionClasses(retryableExceptionClasses);
@@ -124,7 +127,7 @@ public class StatefulRetryStepFactoryBean extends SkipLimitStepFactoryBean {
// Co-ordinate the retry policy with the exception handler:
getStepOperations()
.setExceptionHandler(new SimpleRetryExceptionHandler(retryPolicy, getExceptionHandler()));
.setExceptionHandler(new SimpleRetryExceptionHandler(retryPolicy, getExceptionHandler(), getFatalExceptionClasses()));
ItemWriterRetryPolicy itemProviderRetryPolicy = new ItemWriterRetryPolicy(retryPolicy);

View File

@@ -42,8 +42,9 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
protected void setUp() throws Exception {
RepeatSynchronizationManager.register(context);
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
@@ -59,7 +60,7 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
RetryPolicy retryPolicy = new NeverRetryPolicy();
RuntimeException ex = new RuntimeException("foo");
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex);
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex, new Class[] { Error.class });
// Then pretend to handle the exception in the parent context...
try {
@@ -71,7 +72,8 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
}
assertEquals(0, context.attributeNames().length);
// One for the retry exhausted flag and one for the counter in the delegate exception handler
// One for the retry exhausted flag and one for the counter in the
// delegate exception handler
assertEquals(2, context.getParent().attributeNames().length);
}
@@ -84,24 +86,50 @@ public class SimpleRetryExceptionHandlerTests extends TestCase {
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
RuntimeException ex = new RuntimeException("foo");
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex);
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex, new Class[] { Error.class });
// Then pretend to handle the exception in the parent context...
handler.handleException(context.getParent(), ex);
assertEquals(0, context.attributeNames().length);
assertEquals(0, context.getParent().attributeNames().length);
}
/**
* Test method for
* {@link org.springframework.batch.core.step.item.SimpleRetryExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, java.lang.Throwable)}.
*/
public void testRethrowWhenFatal() throws Throwable {
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
RuntimeException ex = new RuntimeException("foo");
SimpleRetryExceptionHandler handler = getHandlerAfterRetry(retryPolicy, ex, new Class[] { RuntimeException.class });
// Then pretend to handle the exception in the parent context...
try {
handler.handleException(context.getParent(), ex);
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
assertEquals(ex, e);
}
assertEquals(0, context.attributeNames().length);
// One for the counter in the delegate exception handler
assertEquals(1, context.getParent().attributeNames().length);
}
/**
* @param retryPolicy
* @param ex
* @return
*/
private SimpleRetryExceptionHandler getHandlerAfterRetry(RetryPolicy retryPolicy, RuntimeException ex) {
private SimpleRetryExceptionHandler getHandlerAfterRetry(RetryPolicy retryPolicy, RuntimeException ex, Class[] fatalExceptions) {
// Always rethrow if the retry is exhausted
SimpleRetryExceptionHandler handler = new SimpleRetryExceptionHandler(retryPolicy, new SimpleLimitExceptionHandler(0));
SimpleRetryExceptionHandler handler = new SimpleRetryExceptionHandler(retryPolicy,
new SimpleLimitExceptionHandler(0), fatalExceptions);
// Simulate a failed retry...
RetryContext retryContext = retryPolicy.open(null, null);

View File

@@ -32,8 +32,6 @@ import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.step.item.ItemOrientedStep;
import org.springframework.batch.core.step.item.StatefulRetryStepFactoryBean;
import org.springframework.batch.item.AbstractItemWriter;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemRecoverer;
@@ -125,5 +123,5 @@ public class StatefulRetryStepFactoryBeanTests extends TestCase {
step.execute(new StepExecution(step, jobExecution));
}
}