Skip to content

Commit

Permalink
Feature/215 add to circuit breaker ignore and record exceptions (Reac…
Browse files Browse the repository at this point in the history
…tiveX#217)

* Feature ReactiveX#215 ignore and record exceptions

* Feature ReactiveX#215 ignore and record exceptions

* 215 Cleanup and changed test

* 215 removed super
  • Loading branch information
rLitto authored and RobWin committed Mar 23, 2018
1 parent 7bfe331 commit 33f137a
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package io.github.resilience4j.circuitbreaker;

import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Predicate;


Expand All @@ -31,13 +33,14 @@ public class CircuitBreakerConfig {
public static final int DEFAULT_WAIT_DURATION_IN_OPEN_STATE = 60; // Seconds
public static final int DEFAULT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATE = 10;
public static final int DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE = 100;
public static final Predicate<Throwable> DEFAULT_RECORD_FAILURE_PREDICATE = (throwable) -> true;

private float failureRateThreshold = DEFAULT_MAX_FAILURE_THRESHOLD;
private int ringBufferSizeInHalfOpenState = DEFAULT_RING_BUFFER_SIZE_IN_HALF_OPEN_STATE;
private int ringBufferSizeInClosedState = DEFAULT_RING_BUFFER_SIZE_IN_CLOSED_STATE;
private Duration waitDurationInOpenState = Duration.ofSeconds(DEFAULT_WAIT_DURATION_IN_OPEN_STATE);
// The default exception predicate counts all exceptions as failures.
private Predicate<? super Throwable> recordFailurePredicate = (exception) -> true;
private Predicate<Throwable> recordFailurePredicate = DEFAULT_RECORD_FAILURE_PREDICATE;

private CircuitBreakerConfig(){
}
Expand All @@ -58,10 +61,10 @@ public int getRingBufferSizeInClosedState() {
return ringBufferSizeInClosedState;
}

public Predicate<? super Throwable> getRecordFailurePredicate() {
public Predicate<Throwable> getRecordFailurePredicate() {
return recordFailurePredicate;
}

/**
* Returns a builder to create a custom CircuitBreakerConfig.
*
Expand All @@ -81,6 +84,12 @@ public static CircuitBreakerConfig ofDefaults(){
}

public static class Builder {
private Predicate<Throwable> recordFailurePredicate;
private Predicate<Throwable> errorRecordingPredicate;
@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] recordExceptions = new Class[0];
@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] ignoreExceptions = new Class[0];

private CircuitBreakerConfig config = new CircuitBreakerConfig();

Expand Down Expand Up @@ -158,8 +167,54 @@ public Builder ringBufferSizeInClosedState(int ringBufferSizeInClosedState) {
* @param predicate the Predicate which evaluates if an exception should be recorded as a failure and thus trigger the CircuitBreaker
* @return the CircuitBreakerConfig.Builder
*/
public Builder recordFailure(Predicate<? super Throwable> predicate) {
config.recordFailurePredicate = predicate;
public Builder recordFailure(Predicate<Throwable> predicate) {
this.recordFailurePredicate = predicate;
return this;
}

/**
* Configures a list of error classes that are recorded as a failure and thus increase the failure rate.
* Any exception matching or inheriting from one of the list should count as a failure, unless ignored via
* @see #ignoreExceptions(Class[]) ). Ignoring an exception has priority over recording an exception.
*
* Example:
* recordExceptions(Throwable.class) and ignoreExceptions(RuntimeException.class)
* would capture all Errors and checked Exceptions, and ignore unchecked
*
* For a more sophisticated exception management use the
* @see #recordFailure(Predicate) method
*
* @param errorClasses the error classes that are recorded
* @return the CircuitBreakerConfig.Builder
*/
@SafeVarargs
public final Builder recordExceptions(Class<? extends Throwable>... errorClasses) {
this.recordExceptions = errorClasses != null ? errorClasses : new Class[0];
return this;
}

/**
* Configures a list of error classes that are ignored as a failure and thus do not increase the failure rate.
* Any exception matching or inheriting from one of the list will not count as a failure, even if marked via
* @see #recordExceptions(Class[]) . Ignoring an exception has priority over recording an exception.
*
* Example:
* ignoreExceptions(Throwable.class) and recordExceptions(Exception.class)
* would capture nothing
*
* Example:
* ignoreExceptions(Exception.class) and recordExceptions(Throwable.class)
* would capture Errors
*
* For a more sophisticated exception management use the
* @see #recordFailure(Predicate) method
*
* @param errorClasses the error classes that are recorded
* @return the CircuitBreakerConfig.Builder
*/
@SafeVarargs
public final Builder ignoreExceptions(Class<? extends Throwable>... errorClasses) {
this.ignoreExceptions = errorClasses != null ? errorClasses : new Class[0];
return this;
}

Expand All @@ -169,7 +224,41 @@ public Builder recordFailure(Predicate<? super Throwable> predicate) {
* @return the CircuitBreakerConfig
*/
public CircuitBreakerConfig build() {
buildErrorRecordingPredicate();
return config;
}

private void buildErrorRecordingPredicate() {
this.errorRecordingPredicate =
getRecordingPredicate()
.and(buildIgnoreExceptionsPredicate()
.orElse(DEFAULT_RECORD_FAILURE_PREDICATE));
config.recordFailurePredicate = errorRecordingPredicate;
}

private Predicate<Throwable> getRecordingPredicate() {
return buildRecordExceptionsPredicate()
.map(predicate -> recordFailurePredicate != null ? predicate.or(recordFailurePredicate) : predicate)
.orElseGet(() -> recordFailurePredicate != null ? recordFailurePredicate : DEFAULT_RECORD_FAILURE_PREDICATE);
}

private Optional<Predicate<Throwable>> buildRecordExceptionsPredicate() {
return Arrays.stream(recordExceptions)
.distinct()
.map(Builder::makePredicate)
.reduce(Predicate::or);
}

private Optional<Predicate<Throwable>> buildIgnoreExceptionsPredicate() {
return Arrays.stream(ignoreExceptions)
.distinct()
.map(Builder::makePredicate)
.reduce(Predicate::or)
.map(Predicate::negate);
}

static Predicate<Throwable> makePredicate(Class<? extends Throwable> exClass) {
return (Throwable e) -> exClass.isAssignableFrom(e.getClass());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
import org.junit.Test;

import java.time.Duration;
import java.util.function.Predicate;

import static org.assertj.core.api.BDDAssertions.then;

public class CircuitBreakerConfigTest {

public static final Predicate<Throwable> TEST_PREDICATE = e -> "test".equals(e.getMessage());

@Test(expected = IllegalArgumentException.class)
public void zeroMaxFailuresShouldFail() {
CircuitBreakerConfig.custom().failureRateThreshold(0).build();
Expand Down Expand Up @@ -97,9 +100,102 @@ public void shouldSetWaitInterval() {
}

@Test()
public void shouldUseCustomExceptionPredicate() {
public void shouldUseRecordFailureThrowablePredicate() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordFailure((Throwable throwable) -> true).build();
then(circuitBreakerConfig.getRecordFailurePredicate()).isNotNull();
.recordFailure(TEST_PREDICATE).build();
then(circuitBreakerConfig.getRecordFailurePredicate().test(new Error("test"))).isEqualTo(true);
then(circuitBreakerConfig.getRecordFailurePredicate().test(new Error("fail"))).isEqualTo(false);
then(circuitBreakerConfig.getRecordFailurePredicate().test(new RuntimeException("test"))).isEqualTo(true);
then(circuitBreakerConfig.getRecordFailurePredicate().test(new Error())).isEqualTo(false);
then(circuitBreakerConfig.getRecordFailurePredicate().test(new RuntimeException())).isEqualTo(false);
}

private static class ExtendsException extends Exception {
public ExtendsException() { }
public ExtendsException(String message) { super(message); }
}
private static class ExtendsRuntimeException extends RuntimeException {}
private static class ExtendsExtendsException extends ExtendsException {}
private static class ExtendsException2 extends Exception {};
private static class ExtendsError extends Error {};

@Test()
public void shouldUseIgnoreExceptionToBuildPredicate() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.ignoreExceptions(RuntimeException.class, ExtendsExtendsException.class).build();
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
then(failurePredicate.test(new Exception())).isEqualTo(true); // not explicitly excluded
then(failurePredicate.test(new ExtendsError())).isEqualTo(true); // not explicitly excluded
then(failurePredicate.test(new ExtendsException())).isEqualTo(true); // not explicitly excluded
then(failurePredicate.test(new ExtendsException2())).isEqualTo(true); // not explicitly excluded
then(failurePredicate.test(new RuntimeException())).isEqualTo(false); // explicitly excluded
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // inherits excluded from ExtendsException
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // explicitly excluded
}

@Test()
public void shouldUseRecordExceptionToBuildPredicate() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordExceptions(RuntimeException.class, ExtendsExtendsException.class).build();
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(true); // inherits included from ExtendsException
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(true); // explicitly included
}

@Test()
public void shouldUseIgnoreExceptionOverRecordToBuildPredicate() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordExceptions(RuntimeException.class, ExtendsExtendsException.class)
.ignoreExceptions(ExtendsException.class, ExtendsRuntimeException.class)
.build();
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // explicitly excluded
then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // explicitly excluded
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException
}

@Test()
public void shouldUseBothRecordToBuildPredicate() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordFailure(TEST_PREDICATE) //1
.recordExceptions(RuntimeException.class, ExtendsExtendsException.class) //2
.ignoreExceptions(ExtendsException.class, ExtendsRuntimeException.class) //3
.build();
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new Exception("test"))).isEqualTo(true); // explicitly included by 1
then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // ot explicitly included
then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // explicitly excluded by 3
then(failurePredicate.test(new ExtendsException("test"))).isEqualTo(false); // explicitly excluded by 3 even if included by 1
then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included
then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included by 2
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // explicitly excluded by 3
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException by 3
}

@Test()
public void builderMakePredicateShouldBuildPredicateAcceptingChildClass() {
final Predicate<Throwable> predicate = CircuitBreakerConfig.Builder.makePredicate(RuntimeException.class);
then(predicate.test(new RuntimeException())).isEqualTo(true);
then(predicate.test(new Exception())).isEqualTo(false);
then(predicate.test(new Throwable())).isEqualTo(false);
then(predicate.test(new IllegalArgumentException())).isEqualTo(true);
then(predicate.test(new RuntimeException() {
})).isEqualTo(true);
then(predicate.test(new Exception() {
})).isEqualTo(false);

}



}

0 comments on commit 33f137a

Please sign in to comment.