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

@@ -31,6 +31,7 @@ import org.springframework.batch.repeat.context.RepeatContextSupport;
import org.springframework.batch.repeat.support.RepeatSynchronizationManager;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryException;
import org.springframework.batch.retry.StubItemKeyGeneratorRecoverer;
import org.springframework.batch.retry.callback.ItemWriterRetryCallback;
import org.springframework.batch.retry.context.RetryContextSupport;
@@ -46,12 +47,6 @@ public class ItemWriterRetryPolicyTests extends TestCase {
private List list = new ArrayList();
private ItemKeyGenerator keyGenerator = new ItemKeyGenerator() {
public Object getKey(Object item) {
return item;
}
};
protected void setUp() throws Exception {
super.setUp();
// The list simulates a failed delivery, redelivery of the same message,
@@ -294,10 +289,96 @@ public class ItemWriterRetryPolicyTests extends TestCase {
MapRetryContextCache cache = new MapRetryContextCache();
policy.setRetryContextCache(cache);
cache.put("foo", new RetryContextSupport(null));
assertTrue(policy.hasFailed(null, keyGenerator , "foo"));
assertTrue(policy.hasFailed(null, "foo"));
}
private static class MockFailedItemProvider extends ListItemReader implements ItemKeyGenerator, FailedItemIdentifier {
public void testKeyGeneratorNotConsistentAfterFailure() throws Throwable {
AbstractItemWriter writer = new AbstractItemWriter() {
public void write(Object data) {
// This simulates what happens if someone uses a primary key
// for hasCode and equals and then relies on default key
// generator
((StringHolder) data).string = ((StringHolder) data).string + (count++);
throw new RuntimeException("Barf!");
}
};
policy = new ItemWriterRetryPolicy();
policy.setDelegate(new SimpleRetryPolicy(3));
StringHolder item = new StringHolder("bar");
ItemWriterRetryCallback callback = new ItemWriterRetryCallback(item, writer);
RetryContext context = policy.open(callback, null);
assertNotNull(context);
try {
callback.doWithRetry(context);
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
assertEquals("Barf!", e.getMessage());
try {
policy.registerThrowable(context, e);
fail("Expected RetryException");
}
catch (RetryException ex) {
String message = ex.getMessage();
assertTrue("Message doesn't contain 'inconsistent': " + message, message.indexOf("inconsistent") >= 0);
}
assertEquals(0, context.getRetryCount());
}
}
public void testCacheCapacity() throws Exception {
policy = new ItemWriterRetryPolicy();
policy.setDelegate(new SimpleRetryPolicy(1));
policy.setRetryContextCache(new MapRetryContextCache(1));
AbstractItemWriter writer = new AbstractItemWriter() {
public void write(Object data) {
count++;
list.add(data);
}
};
RetryContext context;
context = policy.open(new ItemWriterRetryCallback("foo", writer), null);
policy.registerThrowable(context, null);
assertEquals(0, context.getRetryCount());
context = policy.open(new ItemWriterRetryCallback("bar", writer), null);
try {
policy.registerThrowable(context, new RuntimeException("foo"));
fail("Expected RetryException");
}
catch (RetryException e) {
String message = e.getMessage();
assertTrue("Message does not contain 'capacity': " + message, message.indexOf("capacity") >= 0);
}
}
public void testCacheCapacityNotReachedIfRecovered() throws Exception {
policy = new ItemWriterRetryPolicy();
policy.setDelegate(new SimpleRetryPolicy(1));
policy.setRetryContextCache(new MapRetryContextCache(2));
AbstractItemWriter writer = new AbstractItemWriter() {
public void write(Object data) {
count++;
list.add(data);
}
};
RetryContext context;
context = policy.open(new ItemWriterRetryCallback("foo", writer), null);
policy.registerThrowable(context, null);
assertEquals(0, context.getRetryCount());
policy.registerThrowable(context, new RuntimeException("foo"));
context = policy.open(new ItemWriterRetryCallback("bar", writer), null);
policy.registerThrowable(context, null);
policy.handleRetryExhausted(context);
context = policy.open(new ItemWriterRetryCallback("spam", writer), null);
policy.registerThrowable(context, null);
assertEquals(0, context.getRetryCount());
}
private static class MockFailedItemProvider extends ListItemReader implements ItemKeyGenerator,
FailedItemIdentifier {
private int hasFailedCount = 0;
@@ -316,4 +397,41 @@ public class ItemWriterRetryPolicyTests extends TestCase {
}
private static class StringHolder {
private String string;
/**
* @param string
*/
public StringHolder(String string) {
this.string = string;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
return string.equals(((StringHolder) obj).string);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return string.hashCode();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
return "String: " + string + " (hash = " + hashCode() + ")";
}
}
}