Skip to content

Commit

Permalink
Basic integration between statistics and circuit breaker
Browse files Browse the repository at this point in the history
Really just a check that the existing metrics make sense. Next
step is to provide statistics for each circuit including
information on circuit status and timeouts.
  • Loading branch information
Dave Syer committed Aug 23, 2016
1 parent 9af84c0 commit f475848
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 1 deletion.
Expand Up @@ -37,7 +37,7 @@ 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 (!isExhausted(context) || isGlobal(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.
Expand Down Expand Up @@ -69,6 +69,10 @@ public <T, E extends Throwable> void onError(RetryContext context,
}
}

private boolean isGlobal(RetryContext context) {
return context.hasAttribute("state.global");
}

private boolean isExhausted(RetryContext context) {
return context.hasAttribute(RetryContext.EXHAUSTED);
}
Expand Down
@@ -0,0 +1,134 @@
/*
* Copyright 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 org.junit.Before;
import org.junit.Test;
import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.RetryStatistics;
import org.springframework.retry.policy.CircuitBreakerRetryPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.DefaultRetryState;
import org.springframework.retry.support.RetryTemplate;

/**
* @author Dave Syer
*
*/
public class CircuitBreakerStatisticsTests {

private static final String RECOVERED = "RECOVERED";
private static final String RESULT = "RESULT";
private RetryTemplate retryTemplate;
private RecoveryCallback<Object> recovery;
private MockRetryCallback callback;
private DefaultRetryState state;

private StatisticsRepository repository = new DefaultStatisticsRepository();
private StatisticsListener listener = new StatisticsListener(repository);

@Before
public void init() {
this.callback = new MockRetryCallback();
this.recovery = new RecoveryCallback<Object>() {
@Override
public Object recover(RetryContext context) throws Exception {
return RECOVERED;
}
};
this.retryTemplate = new RetryTemplate();
retryTemplate.setListeners(new RetryListener[] {listener});
this.callback.setAttemptsBeforeSuccess(1);
// No rollback by default (so exceptions are not rethrown)
this.state = new DefaultRetryState("retry", new BinaryExceptionClassifier(false));
}

@Test
public void testCircuitOpenWhenNotRetryable() throws Throwable {
this.retryTemplate
.setRetryPolicy(new CircuitBreakerRetryPolicy(new NeverRetryPolicy()));
Object result = this.retryTemplate.execute(this.callback, this.recovery,
this.state);
RetryStatistics stats = repository.findOne("test");
// System.err.println(stats);
assertEquals(1, stats.getStartedCount());
assertEquals(RECOVERED, result);
result = this.retryTemplate.execute(this.callback, this.recovery, this.state);
assertEquals(RECOVERED, result);
assertEquals("There should be two recoveries", 2, stats.getRecoveryCount());
assertEquals("There should only be one error because the circuit is now open", 1, stats.getErrorCount());
}

@Test
public void testCircuitOpenWithNoRecovery() throws Throwable {
this.retryTemplate
.setRetryPolicy(new CircuitBreakerRetryPolicy(new NeverRetryPolicy()));
this.retryTemplate.setThrowLastExceptionOnExhausted(true);
try {
this.retryTemplate.execute(this.callback, this.state);
} catch (Exception e) {
}
try {
this.retryTemplate.execute(this.callback, this.state);
} catch (Exception e) {
}
RetryStatistics stats = repository.findOne("test");
assertEquals("There should be two recoveries", 2, stats.getAbortCount());
assertEquals("There should only be one error because the circuit is now open", 1, stats.getErrorCount());
}

protected static class MockRetryCallback implements RetryCallback<Object, Exception> {

private int attemptsBeforeSuccess;

private Exception exceptionToThrow = new Exception();

private RetryContext status;

@Override
public Object doWithRetry(RetryContext status) throws Exception {
status.setAttribute(RetryContext.NAME, "test");
this.status = status;
int attempts = (Integer) status.getAttribute("attempts");
attempts++;
status.setAttribute("attempts", attempts);
if (attempts <= this.attemptsBeforeSuccess) {
throw this.exceptionToThrow;
}
return RESULT;
}

public boolean isOpen() {
return status != null && status.getAttribute("open")==Boolean.TRUE;
}

public void setAttemptsBeforeSuccess(int attemptsBeforeSuccess) {
this.attemptsBeforeSuccess = attemptsBeforeSuccess;
}

public void setExceptionToThrow(Exception exceptionToThrow) {
this.exceptionToThrow = exceptionToThrow;
}
}

}

0 comments on commit f475848

Please sign in to comment.