Skip to content

Commit

Permalink
Issue #568: Changed the logic in the CircuitBreaker to handle excepti…
Browse files Browse the repository at this point in the history
…on so that it is also possible to count exceptions as a success. (#573)

The list of ignored exceptions has always precedence. If an exception is ignored it neither counts as a success nor failure.
If the list of recorded exceptions only contains some exceptions, all others count as a success, unless they are not part of the list of ignored exceptions.
  • Loading branch information
RobWin committed Aug 16, 2019
1 parent dcaeb44 commit 476f863
Show file tree
Hide file tree
Showing 14 changed files with 278 additions and 138 deletions.
Expand Up @@ -37,9 +37,15 @@ public class CircuitBreakerConfig {
public static final int DEFAULT_MINIMUM_NUMBER_OF_CALLS = 100;
public static final int DEFAULT_SLIDING_WINDOW_SIZE = 100;
public static final int DEFAULT_SLOW_CALL_DURATION_THRESHOLD = 60; // Seconds
private static final Predicate<Throwable> DEFAULT_RECORD_FAILURE_PREDICATE = throwable -> true;
private static final Predicate<Throwable> DEFAULT_RECORD_EXCEPTION_PREDICATE = throwable -> true;
private static final Predicate<Throwable> DEFAULT_IGNORE_EXCEPTION_PREDICATE = throwable -> false;
public static final SlidingWindow DEFAULT_SLIDING_WINDOW_TYPE = SlidingWindow.COUNT_BASED;

// The default exception predicate counts all exceptions as failures.
private Predicate<Throwable> recordExceptionPredicate = DEFAULT_RECORD_EXCEPTION_PREDICATE;
// The default exception predicate ignores no exceptions.
private Predicate<Throwable> ignoreExceptionPredicate = DEFAULT_IGNORE_EXCEPTION_PREDICATE;

@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] recordExceptions = new Class[0];
@SuppressWarnings("unchecked")
Expand All @@ -51,8 +57,6 @@ public class CircuitBreakerConfig {
private SlidingWindow slidingWindowType = DEFAULT_SLIDING_WINDOW_TYPE;
private int minimumNumberOfCalls = DEFAULT_MINIMUM_NUMBER_OF_CALLS;
private Duration waitDurationInOpenState = Duration.ofSeconds(DEFAULT_WAIT_DURATION_IN_OPEN_STATE);
// The default exception predicate counts all exceptions as failures.
private Predicate<Throwable> recordFailurePredicate = DEFAULT_RECORD_FAILURE_PREDICATE;
private boolean automaticTransitionFromOpenToHalfOpenEnabled = false;
private float slowCallRateThreshold = DEFAULT_SLOW_CALL_RATE_THRESHOLD;
private Duration slowCallDurationThreshold = Duration.ofSeconds(DEFAULT_SLOW_CALL_DURATION_THRESHOLD);
Expand Down Expand Up @@ -100,8 +104,12 @@ public int getSlidingWindowSize() {
return slidingWindowSize;
}

public Predicate<Throwable> getRecordFailurePredicate() {
return recordFailurePredicate;
public Predicate<Throwable> getRecordExceptionPredicate() {
return recordExceptionPredicate;
}

public Predicate<Throwable> getIgnoreExceptionPredicate() {
return ignoreExceptionPredicate;
}

public boolean isAutomaticTransitionFromOpenToHalfOpenEnabled() {
Expand All @@ -128,14 +136,20 @@ public Duration getSlowCallDurationThreshold() {
return slowCallDurationThreshold;
}



public static class Builder {

@Nullable
private Predicate<Throwable> recordFailurePredicate;
private Predicate<Throwable> recordExceptionPredicate;
@Nullable
private Predicate<Throwable> ignoreExceptionPredicate;

@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] recordExceptions = new Class[0];
@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] ignoreExceptions = new Class[0];

private float failureRateThreshold = DEFAULT_FAILURE_RATE_THRESHOLD;
private int minimumNumberOfCalls = DEFAULT_MINIMUM_NUMBER_OF_CALLS;
private int permittedNumberOfCallsInHalfOpenState = DEFAULT_PERMITTED_CALLS_IN_HALF_OPEN_STATE;
Expand All @@ -155,7 +169,8 @@ public Builder(CircuitBreakerConfig baseConfig) {
this.failureRateThreshold = baseConfig.failureRateThreshold;
this.ignoreExceptions = baseConfig.ignoreExceptions;
this.recordExceptions = baseConfig.recordExceptions;
this.recordFailurePredicate = baseConfig.recordFailurePredicate;
this.recordExceptionPredicate = baseConfig.recordExceptionPredicate;
this.ignoreExceptionPredicate = baseConfig.ignoreExceptionPredicate;
this.automaticTransitionFromOpenToHalfOpenEnabled = baseConfig.automaticTransitionFromOpenToHalfOpenEnabled;
this.slowCallRateThreshold = baseConfig.slowCallRateThreshold;
this.slowCallDurationThreshold = baseConfig.slowCallDurationThreshold;
Expand Down Expand Up @@ -367,16 +382,38 @@ public Builder slidingWindowType(SlidingWindow slidingWindowType) {
return this;
}

/**
* @deprecated use {@link #recordException(Predicate)} instead.
*/
@Deprecated
public Builder recordFailure(Predicate<Throwable> predicate) {
this.recordExceptionPredicate = predicate;
return this;
}

/**
* Configures a Predicate which evaluates if an exception should be recorded as a failure and thus increase the failure rate.
* The Predicate must return true if the exception should count as a failure. The Predicate must return false, if the exception
* should neither count as a failure nor success.
* should count as a success, unless the exception is explicitly ignored by {@link #ignoreExceptions(Class[])} or {@link #ignoreException(Predicate)}.
*
* @param predicate the Predicate which evaluates if an exception should count as a failure
* @return the CircuitBreakerConfig.Builder
*/
public Builder recordFailure(Predicate<Throwable> predicate) {
this.recordFailurePredicate = predicate;
public Builder recordException(Predicate<Throwable> predicate) {
this.recordExceptionPredicate = predicate;
return this;
}

/**
* Configures a Predicate which evaluates if an exception should be ignored and neither count as a failure nor success.
* The Predicate must return true if the exception should be ignored .
* The Predicate must return false, if the exception should count as a failure.
*
* @param predicate the Predicate which evaluates if an exception should count as a failure
* @return the CircuitBreakerConfig.Builder
*/
public Builder ignoreException(Predicate<Throwable> predicate) {
this.ignoreExceptionPredicate = predicate;
return this;
}

Expand All @@ -390,10 +427,10 @@ public Builder recordFailure(Predicate<Throwable> predicate) {
* <p>
* Example:
* recordExceptions(Throwable.class) and ignoreExceptions(RuntimeException.class)
* would capture all Errors and checked Exceptions, and ignore unchecked
* would capture all Errors and checked Exceptions, and ignore RuntimeExceptions.
* <p>
* For a more sophisticated exception management use the
* @see #recordFailure(Predicate) method
* @see #recordException(Predicate) method
*/
@SuppressWarnings("unchecked")
@SafeVarargs
Expand All @@ -412,14 +449,14 @@ public final Builder recordExceptions(@Nullable Class<? extends Throwable>... er
* <p>
* Example:
* ignoreExceptions(Throwable.class) and recordExceptions(Exception.class)
* would capture nothing
* would capture nothing.
* <p>
* Example:
* ignoreExceptions(Exception.class) and recordExceptions(Throwable.class)
* would capture Errors
* would capture Errors.
* <p>
* For a more sophisticated exception management use the
* @see #recordFailure(Predicate) method
* @see #ignoreException(Predicate) method
*/
@SuppressWarnings("unchecked")
@SafeVarargs
Expand Down Expand Up @@ -466,23 +503,22 @@ public CircuitBreakerConfig build() {
config.recordExceptions = recordExceptions;
config.ignoreExceptions = ignoreExceptions;
config.automaticTransitionFromOpenToHalfOpenEnabled = automaticTransitionFromOpenToHalfOpenEnabled;
config.recordFailurePredicate = createRecordFailurePredicate();
config.recordExceptionPredicate = createRecordExceptionPredicate();
config.ignoreExceptionPredicate = createIgnoreFailurePredicate();
return config;
}

private Predicate<Throwable> createRecordFailurePredicate() {
return createRecordExceptionPredicate()
.and(PredicateCreator.createIgnoreExceptionsPredicate(ignoreExceptions)
.orElse(DEFAULT_RECORD_FAILURE_PREDICATE));
private Predicate<Throwable> createIgnoreFailurePredicate() {
return PredicateCreator.createExceptionsPredicate(ignoreExceptions)
.map(predicate -> ignoreExceptionPredicate != null ? predicate.or(ignoreExceptionPredicate) : predicate)
.orElseGet(() -> ignoreExceptionPredicate != null ? ignoreExceptionPredicate : DEFAULT_IGNORE_EXCEPTION_PREDICATE);
}

private Predicate<Throwable> createRecordExceptionPredicate() {
return PredicateCreator.createRecordExceptionsPredicate(recordExceptions)
.map(predicate -> recordFailurePredicate != null ? predicate.or(recordFailurePredicate) : predicate)
.orElseGet(() -> recordFailurePredicate != null ? recordFailurePredicate : DEFAULT_RECORD_FAILURE_PREDICATE);
return PredicateCreator.createExceptionsPredicate(recordExceptions)
.map(predicate -> recordExceptionPredicate != null ? predicate.or(recordExceptionPredicate) : predicate)
.orElseGet(() -> recordExceptionPredicate != null ? recordExceptionPredicate : DEFAULT_RECORD_EXCEPTION_PREDICATE);
}


}

public enum SlidingWindow {
Expand Down
Expand Up @@ -37,7 +37,6 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

Expand Down Expand Up @@ -155,24 +154,29 @@ public void acquirePermission() {
public void onError(long duration, TimeUnit durationUnit, Throwable throwable) {
// Handle the case if the completable future throw CompletionException wrapping the original exception
// where original exception is the the one to retry not the CompletionException.
Predicate<Throwable> recordFailurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
if (throwable instanceof CompletionException) {
Throwable cause = throwable.getCause();
handleThrowable(duration, durationUnit, recordFailurePredicate, cause);
handleThrowable(duration, durationUnit, cause);
}else{
handleThrowable(duration, durationUnit, recordFailurePredicate, throwable);
handleThrowable(duration, durationUnit, throwable);
}
}

private void handleThrowable(long duration, TimeUnit durationUnit, Predicate<Throwable> recordFailurePredicate, Throwable throwable) {
if (recordFailurePredicate.test(throwable)) {
LOG.debug("CircuitBreaker '{}' recorded a failure:", name, throwable);
publishCircuitErrorEvent(name, duration, durationUnit, throwable);
stateReference.get().onError(duration, durationUnit, throwable);
} else {
private void handleThrowable(long duration, TimeUnit durationUnit, Throwable throwable) {
if(circuitBreakerConfig.getIgnoreExceptionPredicate().test(throwable)){
LOG.debug("CircuitBreaker '{}' ignored an exception:", name, throwable);
releasePermission();
publishCircuitIgnoredErrorEvent(name, duration,durationUnit, throwable);
}
else if(circuitBreakerConfig.getRecordExceptionPredicate().test(throwable)){
LOG.debug("CircuitBreaker '{}' recorded an exception as failure:", name, throwable);
publishCircuitErrorEvent(name, duration, durationUnit, throwable);
stateReference.get().onError(duration, durationUnit, throwable);
}else{
LOG.debug("CircuitBreaker '{}' recorded an exception as success:", name, throwable);
publishSuccessEvent(duration, durationUnit);
stateReference.get().onSuccess(duration, durationUnit);
}
}

@Override
Expand Down

0 comments on commit 476f863

Please sign in to comment.