Add statistics support via a listener
This commit is contained in:
@@ -32,7 +32,27 @@ public interface RetryContext extends AttributeAccessor {
|
||||
* for instance in a retry listener, to accumulate data about the performance of a
|
||||
* retry.
|
||||
*/
|
||||
String STATS_NAME = "stats.name";
|
||||
String NAME = "context.name";
|
||||
|
||||
/**
|
||||
* Retry context attribute name for state key. Can be used to identify a stateful retry from its context.
|
||||
*/
|
||||
String STATE_KEY = "context.state";
|
||||
|
||||
/**
|
||||
* Retry context attribute that is non-null (and true) if the context has been closed.
|
||||
*/
|
||||
String CLOSED = "context.closed";
|
||||
|
||||
/**
|
||||
* Retry context attribute that is non-null (and true) if the recovery path was taken.
|
||||
*/
|
||||
String RECOVERED = "context.recovered";
|
||||
|
||||
/**
|
||||
* Retry context attribute that is non-null (and true) if the retry was exhausted.
|
||||
*/
|
||||
String EXHAUSTED = "context.exhausted";
|
||||
|
||||
/**
|
||||
* Signal to the framework that no more attempts should be made to try or retry the
|
||||
|
||||
@@ -26,7 +26,7 @@ package org.springframework.retry;
|
||||
public interface RetryStatistics {
|
||||
|
||||
/**
|
||||
* @return the number of completed retry attempts (successful or not).
|
||||
* @return the number of completed successful retry attempts.
|
||||
*/
|
||||
int getCompleteCount();
|
||||
|
||||
@@ -54,6 +54,13 @@ public interface RetryStatistics {
|
||||
*/
|
||||
int getAbortCount();
|
||||
|
||||
/**
|
||||
* Get the number of times a recovery callback was applied.
|
||||
*
|
||||
* @return the number of recovered attempts.
|
||||
*/
|
||||
int getRecoveryCount();
|
||||
|
||||
/**
|
||||
* Get an identifier for the retry block for reporting purposes.
|
||||
*
|
||||
|
||||
@@ -77,7 +77,7 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
|
||||
context.setAttribute(RetryContext.STATS_NAME, label);
|
||||
context.setAttribute(RetryContext.NAME, label);
|
||||
|
||||
/*
|
||||
* If we don't copy the invocation carefully it won't keep a reference to
|
||||
|
||||
@@ -170,7 +170,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
}
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
context.setAttribute(RetryContext.STATS_NAME, label);
|
||||
context.setAttribute(RetryContext.NAME, label);
|
||||
try {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.stats;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.retry.RetryStatistics;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultRetryStatistics implements RetryStatistics {
|
||||
|
||||
private String name;
|
||||
private AtomicInteger startedCount = new AtomicInteger();
|
||||
private AtomicInteger completeCount = new AtomicInteger();
|
||||
private AtomicInteger recoveryCount = new AtomicInteger();
|
||||
private AtomicInteger errorCount = new AtomicInteger();
|
||||
private AtomicInteger abortCount = new AtomicInteger();
|
||||
|
||||
DefaultRetryStatistics() {
|
||||
}
|
||||
|
||||
public DefaultRetryStatistics(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public DefaultRetryStatistics(String name, int startedCount, int completeCount,
|
||||
int recoveryCount, int errorCount, int abortCount) {
|
||||
this.name = name;
|
||||
this.startedCount = new AtomicInteger(startedCount);
|
||||
this.completeCount = new AtomicInteger(completeCount);
|
||||
this.recoveryCount = new AtomicInteger(recoveryCount);
|
||||
this.errorCount = new AtomicInteger(errorCount);
|
||||
this.abortCount = new AtomicInteger(abortCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompleteCount() {
|
||||
return completeCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartedCount() {
|
||||
return startedCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorCount() {
|
||||
return errorCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAbortCount() {
|
||||
return abortCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRecoveryCount() {
|
||||
return recoveryCount.get();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setStartedCount(int startedCount) {
|
||||
this.startedCount.set(startedCount);
|
||||
}
|
||||
|
||||
public void setCompleteCount(int completeCount) {
|
||||
this.completeCount.set(completeCount);
|
||||
}
|
||||
|
||||
public void setRecoveryCount(int recoveryCount) {
|
||||
this.recoveryCount.set(recoveryCount);
|
||||
}
|
||||
|
||||
public void setErrorCount(int errorCount) {
|
||||
this.errorCount.set(errorCount);
|
||||
}
|
||||
|
||||
public void setAbortCount(int abortCount) {
|
||||
this.abortCount.set(abortCount);
|
||||
}
|
||||
|
||||
public void incrementStartedCount() {
|
||||
this.startedCount.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementCompleteCount() {
|
||||
this.completeCount.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementRecoveryCount() {
|
||||
this.recoveryCount.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementErrorCount() {
|
||||
this.errorCount.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementAbortCount() {
|
||||
this.abortCount.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultRetryStatistics [name=" + name + ", startedCount=" + startedCount
|
||||
+ ", completeCount=" + completeCount + ", recoveryCount=" + recoveryCount
|
||||
+ ", errorCount=" + errorCount + ", abortCount=" + abortCount + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.stats;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.retry.RetryStatistics;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DefaultStatisticsRepository implements StatisticsRepository {
|
||||
|
||||
private ConcurrentMap<String, DefaultRetryStatistics> map = new ConcurrentHashMap<String, DefaultRetryStatistics>();
|
||||
|
||||
@Override
|
||||
public RetryStatistics findOne(String name) {
|
||||
return map.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<RetryStatistics> findAll() {
|
||||
return new ArrayList<RetryStatistics>(map.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStarted(String name) {
|
||||
getStatistics(name).incrementStartedCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addError(String name) {
|
||||
getStatistics(name).incrementErrorCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRecovery(String name) {
|
||||
getStatistics(name).incrementRecoveryCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addComplete(String name) {
|
||||
getStatistics(name).incrementCompleteCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAbort(String name) {
|
||||
getStatistics(name).incrementAbortCount();
|
||||
}
|
||||
|
||||
private DefaultRetryStatistics getStatistics(String name) {
|
||||
DefaultRetryStatistics stats;
|
||||
if (!map.containsKey(name)) {
|
||||
map.putIfAbsent(name, new DefaultRetryStatistics(name));
|
||||
}
|
||||
stats = map.get(name);
|
||||
return stats;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.stats;
|
||||
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.listener.RetryListenerSupport;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StatisticsListener extends RetryListenerSupport {
|
||||
|
||||
private final StatisticsRepository repository;
|
||||
|
||||
public StatisticsListener(StatisticsRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, E extends Throwable> void close(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
String name = getName(context);
|
||||
if (name != null) {
|
||||
if (!isExhausted(context)) {
|
||||
// If exhausted and stateful then the retry callback was not called. If
|
||||
// exhausted and stateless it was called, but the started counter was
|
||||
// already incremented.
|
||||
repository.addStarted(name);
|
||||
}
|
||||
if (isRecovered(context)) {
|
||||
repository.addRecovery(name);
|
||||
}
|
||||
else if (isExhausted(context)) {
|
||||
repository.addAbort(name);
|
||||
}
|
||||
else if (isClosed(context)) {
|
||||
repository.addComplete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, E extends Throwable> void onError(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
String name = getName(context);
|
||||
if (name != null) {
|
||||
if (!hasState(context)) {
|
||||
// Stateless retry involves starting the retry callback once per error
|
||||
// without closing the context, so we need to increment the started count
|
||||
repository.addStarted(name);
|
||||
}
|
||||
repository.addError(name);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExhausted(RetryContext context) {
|
||||
return context.hasAttribute(RetryContext.EXHAUSTED);
|
||||
}
|
||||
|
||||
private boolean isClosed(RetryContext context) {
|
||||
return context.hasAttribute(RetryContext.CLOSED);
|
||||
}
|
||||
|
||||
private boolean isRecovered(RetryContext context) {
|
||||
return context.hasAttribute(RetryContext.RECOVERED);
|
||||
}
|
||||
|
||||
private boolean hasState(RetryContext context) {
|
||||
return context.hasAttribute(RetryContext.STATE_KEY);
|
||||
}
|
||||
|
||||
private String getName(RetryContext context) {
|
||||
return (String) context.getAttribute(RetryContext.NAME);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.stats;
|
||||
|
||||
import org.springframework.retry.RetryStatistics;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface StatisticsRepository {
|
||||
|
||||
RetryStatistics findOne(String name);
|
||||
|
||||
Iterable<RetryStatistics> findAll();
|
||||
|
||||
void addStarted(String name);
|
||||
|
||||
void addError(String name);
|
||||
|
||||
void addRecovery(String name);
|
||||
|
||||
void addComplete(String name);
|
||||
|
||||
void addAbort(String name);
|
||||
|
||||
}
|
||||
@@ -371,10 +371,12 @@ public class RetryTemplate implements RetryOperations {
|
||||
if (succeeded) {
|
||||
this.retryContextCache.remove(state.getKey());
|
||||
retryPolicy.close(context);
|
||||
context.setAttribute(RetryContext.CLOSED, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
retryPolicy.close(context);
|
||||
context.setAttribute(RetryContext.CLOSED, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,14 +419,14 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
Object key = state.getKey();
|
||||
if (state.isForceRefresh()) {
|
||||
return doOpenInternal(retryPolicy);
|
||||
return doOpenInternal(retryPolicy, state);
|
||||
}
|
||||
|
||||
// If there is no cache hit we can avoid the possible expense of the
|
||||
// cache re-hydration.
|
||||
if (!this.retryContextCache.containsKey(key)) {
|
||||
// The cache is only used if there is a failure.
|
||||
return doOpenInternal(retryPolicy);
|
||||
return doOpenInternal(retryPolicy, state);
|
||||
}
|
||||
|
||||
RetryContext context = this.retryContextCache.get(key);
|
||||
@@ -437,15 +439,23 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
// The cache could have been expired in between calls to
|
||||
// containsKey(), so we have to live with this:
|
||||
return doOpenInternal(retryPolicy);
|
||||
return doOpenInternal(retryPolicy, state);
|
||||
}
|
||||
|
||||
return context;
|
||||
|
||||
}
|
||||
|
||||
private RetryContext doOpenInternal(RetryPolicy retryPolicy, RetryState state) {
|
||||
RetryContext context = retryPolicy.open(RetrySynchronizationManager.getContext());
|
||||
if (state!=null) {
|
||||
context.setAttribute(RetryContext.STATE_KEY, state.getKey());
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private RetryContext doOpenInternal(RetryPolicy retryPolicy) {
|
||||
return retryPolicy.open(RetrySynchronizationManager.getContext());
|
||||
return doOpenInternal(retryPolicy, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -465,10 +475,12 @@ public class RetryTemplate implements RetryOperations {
|
||||
*/
|
||||
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback,
|
||||
RetryContext context, RetryState state) throws Throwable {
|
||||
context.setAttribute(RetryContext.EXHAUSTED, true);
|
||||
if (state != null) {
|
||||
this.retryContextCache.remove(state.getKey());
|
||||
}
|
||||
if (recoveryCallback != null) {
|
||||
context.setAttribute(RetryContext.RECOVERED, true);
|
||||
return recoveryCallback.recover(context);
|
||||
}
|
||||
if (state != null) {
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.stats;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.retry.RecoveryCallback;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryState;
|
||||
import org.springframework.retry.RetryStatistics;
|
||||
import org.springframework.retry.listener.RetryListenerSupport;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.DefaultRetryState;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StatisticsListenerTests {
|
||||
|
||||
private StatisticsRepository repository = new DefaultStatisticsRepository();
|
||||
private StatisticsListener listener = new StatisticsListener(repository);
|
||||
|
||||
@Test
|
||||
public void testStatelessSuccessful() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setListeners(new RetryListenerSupport[] { listener });
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x,
|
||||
Collections.<Class<? extends Throwable>, Boolean>singletonMap(
|
||||
Exception.class, true)));
|
||||
retryTemplate.execute(callback);
|
||||
assertEquals(x, callback.attempts);
|
||||
RetryStatistics stats = repository.findOne("test");
|
||||
// System.err.println(stats);
|
||||
assertNotNull(stats);
|
||||
assertEquals(x, stats.getCompleteCount());
|
||||
assertEquals((x + 1) * x / 2, stats.getStartedCount());
|
||||
assertEquals(stats.getStartedCount(), stats.getErrorCount() + x);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatefulSuccessful() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setListeners(new RetryListenerSupport[] { listener });
|
||||
RetryState state = new DefaultRetryState("foo");
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x,
|
||||
Collections.<Class<? extends Throwable>, Boolean>singletonMap(
|
||||
Exception.class, true)));
|
||||
for (int i = 0; i < x; i++) {
|
||||
try {
|
||||
retryTemplate.execute(callback, state);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
assertEquals(x, callback.attempts);
|
||||
RetryStatistics stats = repository.findOne("test");
|
||||
// System.err.println(stats);
|
||||
assertNotNull(stats);
|
||||
assertEquals(x, stats.getCompleteCount());
|
||||
assertEquals((x + 1) * x / 2, stats.getStartedCount());
|
||||
assertEquals(stats.getStartedCount(), stats.getErrorCount() + x);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatelessUnsuccessful() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setListeners(new RetryListenerSupport[] { listener });
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x + 1);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x,
|
||||
Collections.<Class<? extends Throwable>, Boolean>singletonMap(
|
||||
Exception.class, true)));
|
||||
try {
|
||||
retryTemplate.execute(callback);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// not interested
|
||||
}
|
||||
assertEquals(x, callback.attempts);
|
||||
RetryStatistics stats = repository.findOne("test");
|
||||
assertNotNull(stats);
|
||||
assertEquals(x, stats.getAbortCount());
|
||||
assertEquals((x + 1) * x / 2, stats.getStartedCount());
|
||||
assertEquals(stats.getStartedCount(), stats.getErrorCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatefulUnsuccessful() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setListeners(new RetryListenerSupport[] { listener });
|
||||
RetryState state = new DefaultRetryState("foo");
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x + 1);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x,
|
||||
Collections.<Class<? extends Throwable>, Boolean>singletonMap(
|
||||
Exception.class, true)));
|
||||
for (int i = 0; i < x+1; i++) {
|
||||
try {
|
||||
retryTemplate.execute(callback, state);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
assertEquals(x, callback.attempts);
|
||||
RetryStatistics stats = repository.findOne("test");
|
||||
// System.err.println(stats);
|
||||
assertNotNull(stats);
|
||||
assertEquals(x, stats.getAbortCount());
|
||||
assertEquals((x + 1) * x / 2, stats.getStartedCount());
|
||||
assertEquals(stats.getStartedCount(), stats.getErrorCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatelessRecovery() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setListeners(new RetryListenerSupport[] { listener });
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x + 1);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x,
|
||||
Collections.<Class<? extends Throwable>, Boolean>singletonMap(
|
||||
Exception.class, true)));
|
||||
retryTemplate.execute(callback, new RecoveryCallback<Object>() {
|
||||
@Override
|
||||
public Object recover(RetryContext context) throws Exception {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
assertEquals(x, callback.attempts);
|
||||
RetryStatistics stats = repository.findOne("test");
|
||||
// System.err.println(stats);
|
||||
assertNotNull(stats);
|
||||
assertEquals(x, stats.getRecoveryCount());
|
||||
assertEquals((x + 1) * x / 2, stats.getStartedCount());
|
||||
assertEquals(stats.getStartedCount(), stats.getErrorCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatefulRecovery() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setListeners(new RetryListenerSupport[] { listener });
|
||||
RetryState state = new DefaultRetryState("foo");
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x + 1);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x,
|
||||
Collections.<Class<? extends Throwable>, Boolean>singletonMap(
|
||||
Exception.class, true)));
|
||||
for (int i = 0; i < x+1; i++) {
|
||||
try {
|
||||
retryTemplate.execute(callback, new RecoveryCallback<Object>() {
|
||||
@Override
|
||||
public Object recover(RetryContext context) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}, state);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
assertEquals(x, callback.attempts);
|
||||
RetryStatistics stats = repository.findOne("test");
|
||||
// System.err.println(stats);
|
||||
assertNotNull(stats);
|
||||
assertEquals(x, stats.getRecoveryCount());
|
||||
assertEquals((x + 1) * x / 2, stats.getStartedCount());
|
||||
assertEquals(stats.getStartedCount(), stats.getErrorCount());
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockRetryCallback implements RetryCallback<Object, Exception> {
|
||||
|
||||
private int attempts;
|
||||
|
||||
private int attemptsBeforeSuccess;
|
||||
|
||||
private Exception exceptionToThrow = new Exception();
|
||||
|
||||
@Override
|
||||
public Object doWithRetry(RetryContext status) throws Exception {
|
||||
status.setAttribute(RetryContext.NAME, "test");
|
||||
this.attempts++;
|
||||
if (this.attempts < this.attemptsBeforeSuccess) {
|
||||
throw this.exceptionToThrow;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAttemptsBeforeSuccess(int attemptsBeforeSuccess) {
|
||||
this.attemptsBeforeSuccess = attemptsBeforeSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user