Skip to content

Commit d2da5a5

Browse files
committed
Add support for time based circuit breakers
1 parent 17dc2f9 commit d2da5a5

23 files changed

+1477
-710
lines changed

CHANGES.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
# 2.4.0
2+
3+
### Improvements
4+
5+
- Added time based thresholding support to `CircuitBreaker` via:
6+
- `withFailureThreshold(int failureThreshold, Duration failureThresholdingPeriod)`
7+
- `withFailureThreshold(int failureThreshold, int failureExecutionThreshold, Duration failureThresholdingPeriod)`
8+
- `withFailureRateThreshold(int failureRateThreshold, int failureExecutionThreshold, Duration failureThresholdingPeriod)`
9+
- Added getters to `CircuitBreaker` for existing count based thresholding settings:
10+
- `getFailureThresholdingCapacity()`
11+
- `getSuccessThresholdingCapacity()`
12+
- And added getters to `CircuitBreaker` for new time based thresholding settings:
13+
- `getFailureRateThreshold()`
14+
- `getFailureExecutionThreshold()`
15+
- `getFailureThresholdingPeriod()`
16+
- Added some new metrics to `CircuitBreaker`:
17+
- `getSuccessRate()`
18+
- `getFailureRate()`
19+
- `getExecutionCount()`
20+
21+
### API Changes
22+
23+
- Changed the return type of `CircuitBreaker`'s `getFailureThreshold()` and `getSuccessThreshold()` from `Ratio` to `int`. `getFailureThresholdingCapacity`, `getFailureRateThreshold`, `getFailureExecutionThreshold`, and `getSuccessThresholdingCapacity` provide additional detail about thresholding configuration.
24+
- Removed support for the previously deprecated `CircuitBreaker.withTimeout`. The `Timeout` policy should be used instead.
25+
126
# 2.3.5
227

328
### Bug Fixes
@@ -53,7 +78,7 @@ Added support for `CompletionStage` to the `Fallback` policy.
5378

5479
# 2.3.0
5580

56-
### API Changes
81+
### Behavior Changes
5782

5883
- `FailsafeExecutor.get` and `FailsafeExecutor.run` will no longer wrap `Error` instances in `FailsafeException` before throwing.
5984

src/main/java/net/jodah/failsafe/CircuitBreaker.java

+296-98
Large diffs are not rendered by default.

src/main/java/net/jodah/failsafe/CircuitBreakerExecutor.java

-10
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package net.jodah.failsafe;
1717

18-
import java.time.Duration;
19-
2018
/**
2119
* A PolicyExecutor that handles failures according to a {@link CircuitBreaker}.
2220
*
@@ -36,14 +34,6 @@ protected ExecutionResult preExecute() {
3634
return ExecutionResult.failure(new CircuitBreakerOpenException(policy));
3735
}
3836

39-
@Override
40-
protected boolean isFailure(ExecutionResult result) {
41-
long elapsedNanos = execution.getElapsedAttemptTime().toNanos();
42-
Duration timeout = policy.getTimeout();
43-
boolean timeoutExceeded = timeout != null && elapsedNanos >= timeout.toNanos();
44-
return timeoutExceeded || super.isFailure(result);
45-
}
46-
4737
@Override
4838
protected void onSuccess(ExecutionResult result) {
4939
policy.recordSuccess();

src/main/java/net/jodah/failsafe/internal/CircuitState.java

+18-23
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
*/
1616
package net.jodah.failsafe.internal;
1717

18+
import net.jodah.failsafe.CircuitBreaker;
1819
import net.jodah.failsafe.CircuitBreaker.State;
1920
import net.jodah.failsafe.ExecutionContext;
20-
import net.jodah.failsafe.internal.util.CircularBitSet;
21-
import net.jodah.failsafe.util.Ratio;
2221

2322
import java.time.Duration;
2423

@@ -28,43 +27,39 @@
2827
* @author Jonathan Halterman
2928
*/
3029
public abstract class CircuitState {
31-
static final Ratio ONE_OF_ONE = new Ratio(1, 1);
30+
final CircuitBreaker breaker;
31+
volatile CircuitStats stats;
3232

33-
protected CircularBitSet bitSet;
33+
CircuitState(CircuitBreaker breaker, CircuitStats stats) {
34+
this.breaker = breaker;
35+
this.stats = stats;
36+
}
3437

3538
public abstract boolean allowsExecution();
3639

37-
public abstract State getInternals();
38-
3940
public Duration getRemainingDelay() {
4041
return Duration.ZERO;
4142
}
4243

43-
public int getFailureCount() {
44-
return bitSet.negatives();
45-
}
46-
47-
public Ratio getFailureRatio() {
48-
return bitSet.negativeRatio();
44+
public CircuitStats getStats() {
45+
return stats;
4946
}
5047

51-
public int getSuccessCount() {
52-
return bitSet.positives();
53-
}
54-
55-
public Ratio getSuccessRatio() {
56-
return bitSet.positiveRatio();
57-
}
48+
public abstract State getState();
5849

59-
public void recordFailure(ExecutionContext context) {
50+
public synchronized void recordFailure(ExecutionContext context) {
51+
stats.recordFailure();
52+
checkThreshold(context);
6053
}
6154

62-
public void recordSuccess() {
55+
public synchronized void recordSuccess() {
56+
stats.recordSuccess();
57+
checkThreshold(null);
6358
}
6459

65-
public void setFailureThreshold(Ratio threshold) {
60+
public void handleConfigChange() {
6661
}
6762

68-
public void setSuccessThreshold(Ratio threshold) {
63+
void checkThreshold(ExecutionContext context) {
6964
}
7065
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License
15+
*/
16+
package net.jodah.failsafe.internal;
17+
18+
import net.jodah.failsafe.CircuitBreaker;
19+
import net.jodah.failsafe.internal.TimedCircuitStats.Clock;
20+
21+
/**
22+
* Stats for a circuit breaker.
23+
*/
24+
public interface CircuitStats {
25+
static CircuitStats create(CircuitBreaker breaker, int capacity, boolean supportsTimeBased, CircuitStats oldStats) {
26+
if (supportsTimeBased && breaker.getFailureThresholdingPeriod() != null)
27+
return new TimedCircuitStats(TimedCircuitStats.DEFAULT_BUCKET_COUNT, breaker.getFailureThresholdingPeriod(),
28+
new Clock(), oldStats);
29+
else if (capacity > 1) {
30+
return new CountingCircuitStats(capacity, oldStats);
31+
} else {
32+
return new DefaultCircuitStats();
33+
}
34+
}
35+
36+
default void copyExecutions(CircuitStats oldStats) {
37+
for (int i = 0; i < oldStats.getSuccessCount(); i++)
38+
recordSuccess();
39+
for (int i = 0; i < oldStats.getFailureCount(); i++)
40+
recordSuccess();
41+
}
42+
43+
int getFailureCount();
44+
45+
int getExecutionCount();
46+
47+
int getSuccessCount();
48+
49+
int getFailureRate();
50+
51+
int getSuccessRate();
52+
53+
void recordFailure();
54+
55+
void recordSuccess();
56+
}

src/main/java/net/jodah/failsafe/internal/ClosedState.java

+21-36
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,13 @@
1818
import net.jodah.failsafe.CircuitBreaker;
1919
import net.jodah.failsafe.CircuitBreaker.State;
2020
import net.jodah.failsafe.ExecutionContext;
21-
import net.jodah.failsafe.internal.util.CircularBitSet;
22-
import net.jodah.failsafe.util.Ratio;
2321

2422
public class ClosedState extends CircuitState {
25-
private final CircuitBreaker breaker;
2623
private final CircuitBreakerInternals internals;
2724

2825
public ClosedState(CircuitBreaker breaker, CircuitBreakerInternals internals) {
29-
this.breaker = breaker;
26+
super(breaker, CircuitStats.create(breaker, capacityFor(breaker), true, null));
3027
this.internals = internals;
31-
setFailureThreshold(breaker.getFailureThreshold() != null ? breaker.getFailureThreshold() : ONE_OF_ONE);
3228
}
3329

3430
@Override
@@ -37,47 +33,36 @@ public boolean allowsExecution() {
3733
}
3834

3935
@Override
40-
public State getInternals() {
36+
public State getState() {
4137
return State.CLOSED;
4238
}
4339

4440
@Override
45-
public synchronized void recordFailure(ExecutionContext context) {
46-
bitSet.setNext(false);
47-
checkThreshold(context);
48-
}
49-
50-
@Override
51-
public synchronized void recordSuccess() {
52-
bitSet.setNext(true);
53-
checkThreshold(null);
41+
public synchronized void handleConfigChange() {
42+
stats = CircuitStats.create(breaker, capacityFor(breaker), true, stats);
5443
}
5544

45+
/**
46+
* Checks to see if the the executions and failure thresholds have been exceeded, opening the circuit if so.
47+
*/
5648
@Override
57-
public void setFailureThreshold(Ratio threshold) {
58-
bitSet = new CircularBitSet(threshold.getDenominator(), bitSet);
49+
synchronized void checkThreshold(ExecutionContext context) {
50+
// Execution threshold will only be set for time based thresholding
51+
if (stats.getExecutionCount() >= breaker.getFailureExecutionThreshold()) {
52+
double failureRateThreshold = breaker.getFailureRateThreshold();
53+
if ((failureRateThreshold != 0 && stats.getFailureRate() >= failureRateThreshold) || (failureRateThreshold == 0
54+
&& stats.getFailureCount() >= breaker.getFailureThreshold()))
55+
internals.open(context);
56+
}
5957
}
6058

6159
/**
62-
* Checks to determine if a threshold has been met and the circuit should be opened or closed.
63-
*
64-
* <p>
65-
* When a failure ratio is configured, the circuit is opened after the expected number of executions based on whether
66-
* the ratio was exceeded.
67-
* <p>
68-
* If a failure threshold is configured, the circuit is opened if the expected number of executions fails else it's
69-
* closed if a single execution succeeds.
60+
* Returns the capacity of the breaker in the closed state.
7061
*/
71-
synchronized void checkThreshold(ExecutionContext context) {
72-
Ratio failureRatio = breaker.getFailureThreshold();
73-
74-
// Handle failure threshold ratio
75-
if (failureRatio != null && bitSet.occupiedBits() >= failureRatio.getDenominator()
76-
&& bitSet.negativeRatioValue() >= failureRatio.getValue())
77-
internals.open(context);
78-
79-
// Handle no thresholds configured
80-
else if (failureRatio == null && bitSet.negativeRatioValue() == 1)
81-
internals.open(context);
62+
private static int capacityFor(CircuitBreaker<?> breaker) {
63+
if (breaker.getFailureExecutionThreshold() != 0)
64+
return breaker.getFailureExecutionThreshold();
65+
else
66+
return breaker.getFailureThresholdingCapacity();
8267
}
8368
}

0 commit comments

Comments
 (0)