Skip to content

Commit 78be9f5

Browse files
committed
Replaced CircuitBreaker.allowsExecution with permitting
Presently CircuitBreaker tries to track how many executions are in progress. Standalone executions use two methods for this: allowsExecution followed by preExecute. This commit reverses the permitting process, so rather than tracking how many executions are in progress we simply track how many executions are permitted, and provide a single method to acquire a permit if possible and return whether execution is permitted.
1 parent 2869712 commit 78be9f5

10 files changed

+76
-70
lines changed

CHANGELOG.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ This release introduces breaking changes to the API:
1616
#### Policies
1717

1818
- All policies are now threadsafe and use a builder API. The configuration methods available in the builder are mostly the same as previously with the 2.x policies. Some notes:
19-
- A policy builder can be created via `.builder()`.
20-
- `RetryPolicy` and `CircuitBreaker` can be constructed with default values using `.ofDefaults()`.
21-
- Policy configuration is accessible via a `.getConfig()`.
19+
- A policy builder can be created via `builder()`.
20+
- `RetryPolicy` and `CircuitBreaker` can be constructed with default values using `ofDefaults()`.
21+
- Policy configuration is accessible via a `getConfig()`.
2222
- Policies that have required arguments, such as `Fallback` and `Timeout`, have additional factory methods for creating a policy without using a builder, ex: `Fallback.of(this::connectToBackup)` and `Timeout.of(Duration.ofSeconds(10))`. Optional arguments must be specified through a builder, ex: `Timeout.builder(duration).withInterrupt().build()`
2323

2424
#### Execution and AsyncExecution
@@ -40,6 +40,10 @@ This release introduces breaking changes to the API:
4040
- `withDelayWhen` has been renamed to `withDelayFnWhen`.
4141
- The above method signatures have also been changed to accept a `ContextualSupplier` instead of a `DelayFunction`, since it provides access to the same information.
4242

43+
#### CircuitBreaker
44+
45+
- `allowsExecution()` was removed in favor of `acquirePermit()` and `tryAcquirePermit()`, which are meant to be used with standalone CircuitBreaker usage.
46+
4347
### SPI Changes
4448

4549
The following changes effect the SPI classes, for users who are extending Failsafe with custom schedulers or policies:

src/main/java/dev/failsafe/CircuitBreaker.java

+20-18
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,33 @@ enum State {
9292
CircuitBreakerConfig<R> getConfig();
9393

9494
/**
95-
* Returns whether the circuit allows execution and triggers a state transition if a threshold has been exceeded.
95+
* Attempts to acquire a permit for the circuit breaker and throws {@link CircuitBreakerOpenException} if a permit
96+
* could not be acquired.
97+
*
98+
* @throws CircuitBreakerOpenException
9699
*/
97-
boolean allowsExecution();
100+
void acquirePermit();
101+
102+
/**
103+
* Attempts to acquire a permit to use the circuit breaker and returns whether a permit was acquired.
104+
*/
105+
boolean tryAcquirePermit();
106+
107+
/**
108+
* Opens the circuit.
109+
*/
110+
void open();
98111

99112
/**
100113
* Closes the circuit.
101114
*/
102115
void close();
103116

117+
/**
118+
* Half-opens the circuit.
119+
*/
120+
void halfOpen();
121+
104122
/**
105123
* Gets the state of the circuit.
106124
*/
@@ -162,11 +180,6 @@ enum State {
162180
*/
163181
int getSuccessRate();
164182

165-
/**
166-
* Half-opens the circuit.
167-
*/
168-
void halfOpen();
169-
170183
/**
171184
* Returns whether the circuit is closed.
172185
*/
@@ -182,17 +195,6 @@ enum State {
182195
*/
183196
boolean isOpen();
184197

185-
/**
186-
* Opens the circuit.
187-
*/
188-
void open();
189-
190-
/**
191-
* Records an execution that is about to take place by incrementing the internal executions count. Required for
192-
* standalone CircuitBreaker usage.
193-
*/
194-
void preExecute();
195-
196198
/**
197199
* Records an execution failure.
198200
*/

src/main/java/dev/failsafe/internal/CircuitBreakerExecutor.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,9 @@ public CircuitBreakerExecutor(CircuitBreakerImpl<R> circuitBreaker, int policyIn
3737

3838
@Override
3939
protected ExecutionResult<R> preExecute() {
40-
if (circuitBreaker.allowsExecution()) {
41-
circuitBreaker.preExecute();
42-
return null;
43-
}
44-
return ExecutionResult.failure(new CircuitBreakerOpenException(circuitBreaker));
40+
return circuitBreaker.tryAcquirePermit() ?
41+
null :
42+
ExecutionResult.failure(new CircuitBreakerOpenException(circuitBreaker));
4543
}
4644

4745
@Override

src/main/java/dev/failsafe/internal/CircuitBreakerImpl.java

+14-29
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616
package dev.failsafe.internal;
1717

1818
import dev.failsafe.*;
19+
import dev.failsafe.event.CircuitBreakerStateChangedEvent;
1920
import dev.failsafe.event.EventListener;
2021
import dev.failsafe.spi.DelayablePolicy;
2122
import dev.failsafe.spi.FailurePolicy;
22-
import dev.failsafe.*;
23-
import dev.failsafe.event.CircuitBreakerStateChangedEvent;
2423
import dev.failsafe.spi.PolicyExecutor;
2524

2625
import java.time.Duration;
27-
import java.util.concurrent.atomic.AtomicInteger;
2826
import java.util.concurrent.atomic.AtomicReference;
2927

3028
/**
@@ -40,7 +38,6 @@ public class CircuitBreakerImpl<R> implements CircuitBreaker<R>, FailurePolicy<R
4038

4139
/** Writes guarded by "this" */
4240
protected final AtomicReference<CircuitState<R>> state = new AtomicReference<>();
43-
protected final AtomicInteger currentExecutions = new AtomicInteger();
4441

4542
public CircuitBreakerImpl(CircuitBreakerConfig<R> config) {
4643
this.config = config;
@@ -53,8 +50,13 @@ public CircuitBreakerConfig<R> getConfig() {
5350
}
5451

5552
@Override
56-
public boolean allowsExecution() {
57-
return state.get().allowsExecution();
53+
public boolean tryAcquirePermit() {
54+
return state.get().tryAcquirePermit();
55+
}
56+
57+
@Override
58+
public void acquirePermit() {
59+
state.get().acquirePermit();
5860
}
5961

6062
@Override
@@ -122,11 +124,6 @@ public void open() {
122124
transitionTo(State.OPEN, config.getOpenListener(), null);
123125
}
124126

125-
@Override
126-
public void preExecute() {
127-
currentExecutions.incrementAndGet();
128-
}
129-
130127
@Override
131128
public void recordFailure() {
132129
recordExecutionFailure(null);
@@ -144,11 +141,7 @@ public void recordResult(R result) {
144141

145142
@Override
146143
public void recordSuccess() {
147-
try {
148-
state.get().recordSuccess();
149-
} finally {
150-
currentExecutions.decrementAndGet();
151-
}
144+
state.get().recordSuccess();
152145
}
153146

154147
@Override
@@ -157,14 +150,10 @@ public String toString() {
157150
}
158151

159152
protected void recordResult(R result, Throwable failure) {
160-
try {
161-
if (isFailure(result, failure))
162-
state.get().recordFailure(null);
163-
else
164-
state.get().recordSuccess();
165-
} finally {
166-
currentExecutions.decrementAndGet();
167-
}
153+
if (isFailure(result, failure))
154+
state.get().recordFailure(null);
155+
else
156+
state.get().recordSuccess();
168157
}
169158

170159
/**
@@ -206,11 +195,7 @@ protected void transitionTo(State newState, EventListener<CircuitBreakerStateCha
206195
* Records an execution failure.
207196
*/
208197
protected void recordExecutionFailure(ExecutionContext<R> context) {
209-
try {
210-
state.get().recordFailure(context);
211-
} finally {
212-
currentExecutions.decrementAndGet();
213-
}
198+
state.get().recordFailure(context);
214199
}
215200

216201
/**

src/main/java/dev/failsafe/internal/CircuitState.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import dev.failsafe.CircuitBreaker.State;
1919
import dev.failsafe.CircuitBreakerConfig;
20+
import dev.failsafe.CircuitBreakerOpenException;
2021
import dev.failsafe.ExecutionContext;
2122

2223
import java.time.Duration;
@@ -38,8 +39,6 @@ public abstract class CircuitState<R> {
3839
this.stats = stats;
3940
}
4041

41-
public abstract boolean allowsExecution();
42-
4342
public Duration getRemainingDelay() {
4443
return Duration.ZERO;
4544
}
@@ -53,16 +52,28 @@ public CircuitStats getStats() {
5352
public synchronized void recordFailure(ExecutionContext<R> context) {
5453
stats.recordFailure();
5554
checkThreshold(context);
55+
releasePermit();
5656
}
5757

5858
public synchronized void recordSuccess() {
5959
stats.recordSuccess();
6060
checkThreshold(null);
61+
releasePermit();
6162
}
6263

6364
public void handleConfigChange() {
6465
}
6566

6667
void checkThreshold(ExecutionContext<R> context) {
6768
}
69+
70+
void acquirePermit() {
71+
if (!tryAcquirePermit())
72+
throw new CircuitBreakerOpenException(breaker);
73+
}
74+
75+
abstract boolean tryAcquirePermit();
76+
77+
void releasePermit() {
78+
}
6879
}

src/main/java/dev/failsafe/internal/ClosedState.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public ClosedState(CircuitBreakerImpl<R> breaker) {
2525
}
2626

2727
@Override
28-
public boolean allowsExecution() {
28+
public boolean tryAcquirePermit() {
2929
return true;
3030
}
3131

src/main/java/dev/failsafe/internal/HalfOpenState.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,24 @@
1919
import dev.failsafe.CircuitBreaker.State;
2020
import dev.failsafe.ExecutionContext;
2121

22+
import java.util.concurrent.atomic.AtomicInteger;
23+
2224
public class HalfOpenState<R> extends CircuitState<R> {
25+
protected final AtomicInteger permittedExecutions = new AtomicInteger();
26+
2327
public HalfOpenState(CircuitBreakerImpl<R> breaker) {
2428
super(breaker, CircuitStats.create(breaker, capacityFor(breaker), false, null));
29+
permittedExecutions.set(capacityFor(breaker));
30+
}
31+
32+
@Override
33+
public boolean tryAcquirePermit() {
34+
return permittedExecutions.getAndUpdate(permits -> permits == 0 ? 0 : permits - 1) > 0;
2535
}
2636

27-
/**
28-
* Ensures that the current executions are less than the thresholding capacity.
29-
*/
3037
@Override
31-
public boolean allowsExecution() {
32-
return breaker.currentExecutions.get() < capacityFor(breaker);
38+
public void releasePermit() {
39+
permittedExecutions.incrementAndGet();
3340
}
3441

3542
@Override

src/main/java/dev/failsafe/internal/OpenState.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ public OpenState(CircuitBreakerImpl<R> breaker, CircuitState<R> previousState, D
2929
}
3030

3131
@Override
32-
public boolean allowsExecution() {
32+
public boolean tryAcquirePermit() {
3333
if (System.nanoTime() - startTime >= delayNanos) {
3434
breaker.halfOpen();
35-
return true;
35+
return breaker.tryAcquirePermit();
3636
}
3737

3838
return false;

src/test/java/dev/failsafe/CircuitBreakerTest.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
@Test
2727
public class CircuitBreakerTest {
2828
public void shouldRequireValidDelay() {
29-
assertThrows(() -> CircuitBreaker.builder().withDelay((Duration) null).build(), NullPointerException.class);
29+
assertThrows(() -> CircuitBreaker.builder().withDelay(null).build(), NullPointerException.class);
3030
assertThrows(() -> CircuitBreaker.builder().withDelay(Duration.ofMillis(-1)).build(),
3131
IllegalArgumentException.class);
3232
}
@@ -55,7 +55,6 @@ public void shouldDefaultDelay() throws Throwable {
5555
CircuitBreaker<Object> breaker = CircuitBreaker.ofDefaults();
5656
breaker.recordFailure();
5757
Thread.sleep(100);
58-
breaker.allowsExecution();
5958
assertTrue(breaker.isOpen());
6059
}
6160

src/test/java/dev/failsafe/internal/OpenStateTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@
2525

2626
@Test
2727
public class OpenStateTest {
28-
public void testAllowsExecution() throws Throwable {
28+
public void testTryAcquirePermit() throws Throwable {
2929
// Given
3030
CircuitBreakerImpl<Object> breaker = (CircuitBreakerImpl<Object>) CircuitBreaker.builder()
3131
.withDelay(Duration.ofMillis(100))
3232
.build();
3333
breaker.open();
3434
OpenState<Object> state = new OpenState<>(breaker, new ClosedState<>(breaker), breaker.getConfig().getDelay());
3535
assertTrue(breaker.isOpen());
36-
assertFalse(state.allowsExecution());
36+
assertFalse(state.tryAcquirePermit());
3737

3838
// When
3939
Thread.sleep(110);
4040

4141
// Then
42-
assertTrue(state.allowsExecution());
42+
assertTrue(state.tryAcquirePermit());
4343
assertEquals(breaker.getState(), State.HALF_OPEN);
4444
}
4545

0 commit comments

Comments
 (0)