Skip to content

Commit 33f137a

Browse files
rLittoRobWin
authored andcommitted
Feature/215 add to circuit breaker ignore and record exceptions (ReactiveX#217)
* Feature ReactiveX#215 ignore and record exceptions * Feature ReactiveX#215 ignore and record exceptions * 215 Cleanup and changed test * 215 removed super
1 parent 7bfe331 commit 33f137a

File tree

2 files changed

+193
-8
lines changed

2 files changed

+193
-8
lines changed

resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package io.github.resilience4j.circuitbreaker;
2020

2121
import java.time.Duration;
22+
import java.util.Arrays;
23+
import java.util.Optional;
2224
import java.util.function.Predicate;
2325

2426

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

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

4245
private CircuitBreakerConfig(){
4346
}
@@ -58,10 +61,10 @@ public int getRingBufferSizeInClosedState() {
5861
return ringBufferSizeInClosedState;
5962
}
6063

61-
public Predicate<? super Throwable> getRecordFailurePredicate() {
64+
public Predicate<Throwable> getRecordFailurePredicate() {
6265
return recordFailurePredicate;
6366
}
64-
67+
6568
/**
6669
* Returns a builder to create a custom CircuitBreakerConfig.
6770
*
@@ -81,6 +84,12 @@ public static CircuitBreakerConfig ofDefaults(){
8184
}
8285

8386
public static class Builder {
87+
private Predicate<Throwable> recordFailurePredicate;
88+
private Predicate<Throwable> errorRecordingPredicate;
89+
@SuppressWarnings("unchecked")
90+
private Class<? extends Throwable>[] recordExceptions = new Class[0];
91+
@SuppressWarnings("unchecked")
92+
private Class<? extends Throwable>[] ignoreExceptions = new Class[0];
8493

8594
private CircuitBreakerConfig config = new CircuitBreakerConfig();
8695

@@ -158,8 +167,54 @@ public Builder ringBufferSizeInClosedState(int ringBufferSizeInClosedState) {
158167
* @param predicate the Predicate which evaluates if an exception should be recorded as a failure and thus trigger the CircuitBreaker
159168
* @return the CircuitBreakerConfig.Builder
160169
*/
161-
public Builder recordFailure(Predicate<? super Throwable> predicate) {
162-
config.recordFailurePredicate = predicate;
170+
public Builder recordFailure(Predicate<Throwable> predicate) {
171+
this.recordFailurePredicate = predicate;
172+
return this;
173+
}
174+
175+
/**
176+
* Configures a list of error classes that are recorded as a failure and thus increase the failure rate.
177+
* Any exception matching or inheriting from one of the list should count as a failure, unless ignored via
178+
* @see #ignoreExceptions(Class[]) ). Ignoring an exception has priority over recording an exception.
179+
*
180+
* Example:
181+
* recordExceptions(Throwable.class) and ignoreExceptions(RuntimeException.class)
182+
* would capture all Errors and checked Exceptions, and ignore unchecked
183+
*
184+
* For a more sophisticated exception management use the
185+
* @see #recordFailure(Predicate) method
186+
*
187+
* @param errorClasses the error classes that are recorded
188+
* @return the CircuitBreakerConfig.Builder
189+
*/
190+
@SafeVarargs
191+
public final Builder recordExceptions(Class<? extends Throwable>... errorClasses) {
192+
this.recordExceptions = errorClasses != null ? errorClasses : new Class[0];
193+
return this;
194+
}
195+
196+
/**
197+
* Configures a list of error classes that are ignored as a failure and thus do not increase the failure rate.
198+
* Any exception matching or inheriting from one of the list will not count as a failure, even if marked via
199+
* @see #recordExceptions(Class[]) . Ignoring an exception has priority over recording an exception.
200+
*
201+
* Example:
202+
* ignoreExceptions(Throwable.class) and recordExceptions(Exception.class)
203+
* would capture nothing
204+
*
205+
* Example:
206+
* ignoreExceptions(Exception.class) and recordExceptions(Throwable.class)
207+
* would capture Errors
208+
*
209+
* For a more sophisticated exception management use the
210+
* @see #recordFailure(Predicate) method
211+
*
212+
* @param errorClasses the error classes that are recorded
213+
* @return the CircuitBreakerConfig.Builder
214+
*/
215+
@SafeVarargs
216+
public final Builder ignoreExceptions(Class<? extends Throwable>... errorClasses) {
217+
this.ignoreExceptions = errorClasses != null ? errorClasses : new Class[0];
163218
return this;
164219
}
165220

@@ -169,7 +224,41 @@ public Builder recordFailure(Predicate<? super Throwable> predicate) {
169224
* @return the CircuitBreakerConfig
170225
*/
171226
public CircuitBreakerConfig build() {
227+
buildErrorRecordingPredicate();
172228
return config;
173229
}
230+
231+
private void buildErrorRecordingPredicate() {
232+
this.errorRecordingPredicate =
233+
getRecordingPredicate()
234+
.and(buildIgnoreExceptionsPredicate()
235+
.orElse(DEFAULT_RECORD_FAILURE_PREDICATE));
236+
config.recordFailurePredicate = errorRecordingPredicate;
237+
}
238+
239+
private Predicate<Throwable> getRecordingPredicate() {
240+
return buildRecordExceptionsPredicate()
241+
.map(predicate -> recordFailurePredicate != null ? predicate.or(recordFailurePredicate) : predicate)
242+
.orElseGet(() -> recordFailurePredicate != null ? recordFailurePredicate : DEFAULT_RECORD_FAILURE_PREDICATE);
243+
}
244+
245+
private Optional<Predicate<Throwable>> buildRecordExceptionsPredicate() {
246+
return Arrays.stream(recordExceptions)
247+
.distinct()
248+
.map(Builder::makePredicate)
249+
.reduce(Predicate::or);
250+
}
251+
252+
private Optional<Predicate<Throwable>> buildIgnoreExceptionsPredicate() {
253+
return Arrays.stream(ignoreExceptions)
254+
.distinct()
255+
.map(Builder::makePredicate)
256+
.reduce(Predicate::or)
257+
.map(Predicate::negate);
258+
}
259+
260+
static Predicate<Throwable> makePredicate(Class<? extends Throwable> exClass) {
261+
return (Throwable e) -> exClass.isAssignableFrom(e.getClass());
262+
}
174263
}
175264
}

resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfigTest.java

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
import org.junit.Test;
2222

2323
import java.time.Duration;
24+
import java.util.function.Predicate;
2425

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

2728
public class CircuitBreakerConfigTest {
2829

30+
public static final Predicate<Throwable> TEST_PREDICATE = e -> "test".equals(e.getMessage());
31+
2932
@Test(expected = IllegalArgumentException.class)
3033
public void zeroMaxFailuresShouldFail() {
3134
CircuitBreakerConfig.custom().failureRateThreshold(0).build();
@@ -97,9 +100,102 @@ public void shouldSetWaitInterval() {
97100
}
98101

99102
@Test()
100-
public void shouldUseCustomExceptionPredicate() {
103+
public void shouldUseRecordFailureThrowablePredicate() {
101104
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
102-
.recordFailure((Throwable throwable) -> true).build();
103-
then(circuitBreakerConfig.getRecordFailurePredicate()).isNotNull();
105+
.recordFailure(TEST_PREDICATE).build();
106+
then(circuitBreakerConfig.getRecordFailurePredicate().test(new Error("test"))).isEqualTo(true);
107+
then(circuitBreakerConfig.getRecordFailurePredicate().test(new Error("fail"))).isEqualTo(false);
108+
then(circuitBreakerConfig.getRecordFailurePredicate().test(new RuntimeException("test"))).isEqualTo(true);
109+
then(circuitBreakerConfig.getRecordFailurePredicate().test(new Error())).isEqualTo(false);
110+
then(circuitBreakerConfig.getRecordFailurePredicate().test(new RuntimeException())).isEqualTo(false);
111+
}
112+
113+
private static class ExtendsException extends Exception {
114+
public ExtendsException() { }
115+
public ExtendsException(String message) { super(message); }
116+
}
117+
private static class ExtendsRuntimeException extends RuntimeException {}
118+
private static class ExtendsExtendsException extends ExtendsException {}
119+
private static class ExtendsException2 extends Exception {};
120+
private static class ExtendsError extends Error {};
121+
122+
@Test()
123+
public void shouldUseIgnoreExceptionToBuildPredicate() {
124+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
125+
.ignoreExceptions(RuntimeException.class, ExtendsExtendsException.class).build();
126+
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
127+
then(failurePredicate.test(new Exception())).isEqualTo(true); // not explicitly excluded
128+
then(failurePredicate.test(new ExtendsError())).isEqualTo(true); // not explicitly excluded
129+
then(failurePredicate.test(new ExtendsException())).isEqualTo(true); // not explicitly excluded
130+
then(failurePredicate.test(new ExtendsException2())).isEqualTo(true); // not explicitly excluded
131+
then(failurePredicate.test(new RuntimeException())).isEqualTo(false); // explicitly excluded
132+
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // inherits excluded from ExtendsException
133+
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // explicitly excluded
134+
}
135+
136+
@Test()
137+
public void shouldUseRecordExceptionToBuildPredicate() {
138+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
139+
.recordExceptions(RuntimeException.class, ExtendsExtendsException.class).build();
140+
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
141+
then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included
142+
then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // not explicitly included
143+
then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // not explicitly included
144+
then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included
145+
then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included
146+
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(true); // inherits included from ExtendsException
147+
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(true); // explicitly included
148+
}
149+
150+
@Test()
151+
public void shouldUseIgnoreExceptionOverRecordToBuildPredicate() {
152+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
153+
.recordExceptions(RuntimeException.class, ExtendsExtendsException.class)
154+
.ignoreExceptions(ExtendsException.class, ExtendsRuntimeException.class)
155+
.build();
156+
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
157+
then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included
158+
then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // not explicitly included
159+
then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // explicitly excluded
160+
then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included
161+
then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included
162+
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // explicitly excluded
163+
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException
164+
}
165+
166+
@Test()
167+
public void shouldUseBothRecordToBuildPredicate() {
168+
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
169+
.recordFailure(TEST_PREDICATE) //1
170+
.recordExceptions(RuntimeException.class, ExtendsExtendsException.class) //2
171+
.ignoreExceptions(ExtendsException.class, ExtendsRuntimeException.class) //3
172+
.build();
173+
final Predicate<? super Throwable> failurePredicate = circuitBreakerConfig.getRecordFailurePredicate();
174+
then(failurePredicate.test(new Exception())).isEqualTo(false); // not explicitly included
175+
then(failurePredicate.test(new Exception("test"))).isEqualTo(true); // explicitly included by 1
176+
then(failurePredicate.test(new ExtendsError())).isEqualTo(false); // ot explicitly included
177+
then(failurePredicate.test(new ExtendsException())).isEqualTo(false); // explicitly excluded by 3
178+
then(failurePredicate.test(new ExtendsException("test"))).isEqualTo(false); // explicitly excluded by 3 even if included by 1
179+
then(failurePredicate.test(new ExtendsException2())).isEqualTo(false); // not explicitly included
180+
then(failurePredicate.test(new RuntimeException())).isEqualTo(true); // explicitly included by 2
181+
then(failurePredicate.test(new ExtendsRuntimeException())).isEqualTo(false); // explicitly excluded by 3
182+
then(failurePredicate.test(new ExtendsExtendsException())).isEqualTo(false); // inherits excluded from ExtendsException by 3
183+
}
184+
185+
@Test()
186+
public void builderMakePredicateShouldBuildPredicateAcceptingChildClass() {
187+
final Predicate<Throwable> predicate = CircuitBreakerConfig.Builder.makePredicate(RuntimeException.class);
188+
then(predicate.test(new RuntimeException())).isEqualTo(true);
189+
then(predicate.test(new Exception())).isEqualTo(false);
190+
then(predicate.test(new Throwable())).isEqualTo(false);
191+
then(predicate.test(new IllegalArgumentException())).isEqualTo(true);
192+
then(predicate.test(new RuntimeException() {
193+
})).isEqualTo(true);
194+
then(predicate.test(new Exception() {
195+
})).isEqualTo(false);
196+
104197
}
198+
199+
200+
105201
}

0 commit comments

Comments
 (0)