Skip to content

Commit 026456d

Browse files
committed
Introduce policy builders (#304)
* Refactors Failsafe to provide builders for creating policies. Some changes to how things are organized include: - Policies are now defined as interfaces, with a separate impl in the .internal package - A Builder class exists for each policy, to define optional policy configuration - A Config class exists for each policy, to read policy configuration - Event listener registration is still done directly on the policy class itself, in a threadsafe way - Policy metrics are accessible on policies themselves, for example, CircuitBreaker.getFailureCount() The policy SPI now lives in a .spi package and provides: - A base PolicyExecutor for creating custom policies - An AbstractPolicy implmentation that implements ExecutionListeners - FailurePolicy and DelayablePolicy implementations - Other internal types that are exposed to policy implementors, including SyncExecutionInternal, AsyncExecutionInternal, FailsafeFuture, ExecutionResult * Replace DelayFunction with ContextualSupplier Raplce DelayFunction with ContextualSupplier since an ExecutionContext has everything needed now that DelayFunction previous provided. * Improve Javadocs about RetryPolicy and CircuitBreaker defaults * Moved event listeners into separate interfaces, implemented by builders
1 parent 9082546 commit 026456d

File tree

125 files changed

+3971
-3045
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+3971
-3045
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ final class AsyncExecutionImpl<R> extends ExecutionImpl<R> implements AsyncExecu
5454
outerFn = asyncExecution ? Functions.toExecutionAware(innerFn) : innerFn;
5555
outerFn = Functions.toAsync(outerFn, scheduler, future);
5656

57-
for (PolicyExecutor<R, ? extends Policy<R>> policyExecutor : policyExecutors)
57+
for (PolicyExecutor<R> policyExecutor : policyExecutors)
5858
outerFn = policyExecutor.applyAsync(outerFn, scheduler, future);
5959
}
6060

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

Lines changed: 62 additions & 529 deletions
Large diffs are not rendered by default.
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
/*
2+
* Copyright 2016 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;
17+
18+
import net.jodah.failsafe.event.CircuitBreakerStateChangedEvent;
19+
import net.jodah.failsafe.event.EventListener;
20+
import net.jodah.failsafe.internal.CircuitBreakerImpl;
21+
import net.jodah.failsafe.internal.util.Assert;
22+
23+
import java.time.Duration;
24+
import java.util.function.BiPredicate;
25+
import java.util.function.Predicate;
26+
27+
/**
28+
* Builds {@link CircuitBreaker} instances.
29+
* <ul>
30+
* <li>By default, any exception is considered a failure and will be handled by the policy. You can override this by
31+
* specifying your own {@code handle} conditions. The default exception handling condition will only be overridden by
32+
* another condition that handles failure exceptions such as {@link #handle(Class)} or {@link #handleIf(BiPredicate)}.
33+
* Specifying a condition that only handles results, such as {@link #handleResult(Object)} or
34+
* {@link #handleResultIf(Predicate)} will not replace the default exception handling condition.</li>
35+
* <li>If multiple {@code handle} conditions are specified, any condition that matches an execution result or failure
36+
* will trigger policy handling.</li>
37+
* </ul>
38+
* <p>
39+
* Note:
40+
* <ul>
41+
* <li>This class extends {@link DelayablePolicyBuilder} and {@link FailurePolicyBuilder} which offer additional configuration.</li>
42+
* <li>This class is <i>not</i> threadsafe.</li>
43+
* </ul>
44+
* </p>
45+
*
46+
* @param <R> result type
47+
* @author Jonathan Halterman
48+
* @see CircuitBreaker
49+
* @see CircuitBreakerOpenException
50+
*/
51+
public class CircuitBreakerBuilder<R>
52+
extends DelayablePolicyBuilder<CircuitBreakerBuilder<R>, CircuitBreakerConfig<R>, R>
53+
implements CircuitBreakerListeners<CircuitBreakerBuilder<R>, R> {
54+
55+
CircuitBreakerBuilder() {
56+
super(new CircuitBreakerConfig<>());
57+
config.delay = Duration.ofMinutes(1);
58+
config.failureThreshold = 1;
59+
config.failureThresholdingCapacity = 1;
60+
}
61+
62+
/**
63+
* Builds a new {@link CircuitBreaker} using the builder's configuration.
64+
*/
65+
public CircuitBreaker<R> build() {
66+
return new CircuitBreakerImpl<>(new CircuitBreakerConfig<>(config));
67+
}
68+
69+
@Override
70+
public CircuitBreakerBuilder<R> onClose(EventListener<CircuitBreakerStateChangedEvent> listener) {
71+
config.closeListener = Assert.notNull(listener, "runnable");
72+
return this;
73+
}
74+
75+
@Override
76+
public CircuitBreakerBuilder<R> onHalfOpen(EventListener<CircuitBreakerStateChangedEvent> listener) {
77+
config.halfOpenListener = Assert.notNull(listener, "runnable");
78+
return this;
79+
}
80+
81+
@Override
82+
public CircuitBreakerBuilder<R> onOpen(EventListener<CircuitBreakerStateChangedEvent> listener) {
83+
config.openListener = Assert.notNull(listener, "listener");
84+
return this;
85+
}
86+
87+
/**
88+
* Sets the {@code delay} to wait in OPEN state before transitioning to half-open.
89+
*
90+
* @throws NullPointerException if {@code delay} is null
91+
* @throws IllegalArgumentException if {@code delay} < 0
92+
*/
93+
public CircuitBreakerBuilder<R> withDelay(Duration delay) {
94+
Assert.notNull(delay, "delay");
95+
Assert.isTrue(delay.toNanos() >= 0, "delay must be positive");
96+
config.delay = delay;
97+
return this;
98+
}
99+
100+
/**
101+
* Configures count based failure thresholding by setting the number of consecutive failures that must occur when in a
102+
* CLOSED state in order to open the circuit.
103+
* <p>
104+
* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} will also
105+
* be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to OPEN or
106+
* CLOSED.
107+
* </p>
108+
*
109+
* @param failureThreshold The number of consecutive failures that must occur in order to open the circuit
110+
* @throws IllegalArgumentException if {@code failureThreshold} < 1
111+
* @see CircuitBreakerConfig#getFailureThreshold()
112+
*/
113+
public CircuitBreakerBuilder<R> withFailureThreshold(int failureThreshold) {
114+
return withFailureThreshold(failureThreshold, failureThreshold);
115+
}
116+
117+
/**
118+
* Configures count based failure thresholding by setting the ratio of successive failures to executions that must
119+
* occur when in a CLOSED state in order to open the circuit. For example: 5, 10 would open the circuit if 5 out of
120+
* the last 10 executions result in a failure.
121+
* <p>
122+
* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} and
123+
* {@code failureThresholdingCapacity} will also be used when the circuit breaker is in a HALF_OPEN state to determine
124+
* whether to transition back to OPEN or CLOSED.
125+
* </p>
126+
*
127+
* @param failureThreshold The number of failures that must occur in order to open the circuit
128+
* @param failureThresholdingCapacity The capacity for storing execution results when performing failure thresholding
129+
* @throws IllegalArgumentException if {@code failureThreshold} < 1, {@code failureThresholdingCapacity} < 1, or
130+
* {@code failureThreshold} > {@code failureThresholdingCapacity}
131+
* @see CircuitBreakerConfig#getFailureThreshold()
132+
* @see CircuitBreakerConfig#getFailureExecutionThreshold()
133+
*/
134+
public CircuitBreakerBuilder<R> withFailureThreshold(int failureThreshold, int failureThresholdingCapacity) {
135+
Assert.isTrue(failureThreshold >= 1, "failureThreshold must be >= 1");
136+
Assert.isTrue(failureThresholdingCapacity >= 1, "failureThresholdingCapacity must be >= 1");
137+
Assert.isTrue(failureThresholdingCapacity >= failureThreshold,
138+
"failureThresholdingCapacity must be >= failureThreshold");
139+
config.failureThreshold = failureThreshold;
140+
config.failureThresholdingCapacity = failureThresholdingCapacity;
141+
return this;
142+
}
143+
144+
/**
145+
* Configures time based failure thresholding by setting the number of failures that must occur within the {@code
146+
* failureThresholdingPeriod} when in a CLOSED state in order to open the circuit.
147+
* <p>
148+
* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} will also
149+
* be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to OPEN or
150+
* CLOSED.
151+
* </p>
152+
*
153+
* @param failureThreshold The number of failures that must occur within the {@code failureThresholdingPeriod} in
154+
* order to open the circuit
155+
* @param failureThresholdingPeriod The period during which failures are compared to the {@code failureThreshold}
156+
* @throws NullPointerException if {@code failureThresholdingPeriod} is null
157+
* @throws IllegalArgumentException if {@code failureThreshold} < 1 or {@code failureThresholdingPeriod} < 10 ms
158+
* @see CircuitBreakerConfig#getFailureThreshold()
159+
* @see CircuitBreakerConfig#getFailureThresholdingPeriod()
160+
*/
161+
public CircuitBreakerBuilder<R> withFailureThreshold(int failureThreshold, Duration failureThresholdingPeriod) {
162+
return withFailureThreshold(failureThreshold, failureThreshold, failureThresholdingPeriod);
163+
}
164+
165+
/**
166+
* Configures time based failure thresholding by setting the number of failures that must occur within the {@code
167+
* failureThresholdingPeriod} when in a CLOSED state in order to open the circuit. The number of executions must also
168+
* exceed the {@code failureExecutionThreshold} within the {@code failureThresholdingPeriod} when in the CLOSED state
169+
* before the circuit can be opened.
170+
* <p>
171+
* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureThreshold} will also
172+
* be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to OPEN or
173+
* CLOSED.
174+
* </p>
175+
*
176+
* @param failureThreshold The number of failures that must occur within the {@code failureThresholdingPeriod} in
177+
* order to open the circuit
178+
* @param failureExecutionThreshold The minimum number of executions that must occur within the {@code
179+
* failureThresholdingPeriod} when in the CLOSED state before the circuit can be opened
180+
* @param failureThresholdingPeriod The period during which failures are compared to the {@code failureThreshold}
181+
* @throws NullPointerException if {@code failureThresholdingPeriod} is null
182+
* @throws IllegalArgumentException if {@code failureThreshold} < 1, {@code failureExecutionThreshold} < 1, {@code
183+
* failureThreshold} > {@code failureExecutionThreshold}, or {@code failureThresholdingPeriod} < 10 ms
184+
* @see CircuitBreakerConfig#getFailureThreshold()
185+
* @see CircuitBreakerConfig#getFailureExecutionThreshold()
186+
* @see CircuitBreakerConfig#getFailureThresholdingPeriod()
187+
*/
188+
public CircuitBreakerBuilder<R> withFailureThreshold(int failureThreshold, int failureExecutionThreshold,
189+
Duration failureThresholdingPeriod) {
190+
Assert.isTrue(failureThreshold >= 1, "failureThreshold must be >= 1");
191+
Assert.isTrue(failureExecutionThreshold >= failureThreshold,
192+
"failureExecutionThreshold must be >= failureThreshold");
193+
assertFailureExecutionThreshold(failureExecutionThreshold);
194+
assertFailureThresholdingPeriod(failureThresholdingPeriod);
195+
config.failureThreshold = failureThreshold;
196+
config.failureThresholdingCapacity = failureThreshold;
197+
config.failureExecutionThreshold = failureExecutionThreshold;
198+
config.failureThresholdingPeriod = failureThresholdingPeriod;
199+
return this;
200+
}
201+
202+
/**
203+
* Configures time based failure rate thresholding by setting the percentage rate of failures, from 1 to 100, that
204+
* must occur within the rolling {@code failureThresholdingPeriod} when in a CLOSED state in order to open the
205+
* circuit. The number of executions must also exceed the {@code failureExecutionThreshold} within the {@code
206+
* failureThresholdingPeriod} before the circuit can be opened.
207+
* <p>
208+
* If a {@link #withSuccessThreshold(int) success threshold} is not configured, the {@code failureExecutionThreshold}
209+
* will also be used when the circuit breaker is in a HALF_OPEN state to determine whether to transition back to open
210+
* or closed.
211+
* </p>
212+
*
213+
* @param failureRateThreshold The percentage rate of failures, from 1 to 100, that must occur in order to open the
214+
* circuit
215+
* @param failureExecutionThreshold The minimum number of executions that must occur within the {@code
216+
* failureThresholdingPeriod} when in the CLOSED state before the circuit can be opened, or in the HALF_OPEN state
217+
* before it can be re-opened or closed
218+
* @param failureThresholdingPeriod The period during which failures are compared to the {@code failureThreshold}
219+
* @throws NullPointerException if {@code failureThresholdingPeriod} is null
220+
* @throws IllegalArgumentException if {@code failureRateThreshold} < 1 or > 100, {@code failureExecutionThreshold} <
221+
* 1, or {@code failureThresholdingPeriod} < 10 ms
222+
* @see CircuitBreakerConfig#getFailureRateThreshold()
223+
* @see CircuitBreakerConfig#getFailureExecutionThreshold()
224+
* @see CircuitBreakerConfig#getFailureThresholdingPeriod()
225+
*/
226+
public CircuitBreakerBuilder<R> withFailureRateThreshold(int failureRateThreshold, int failureExecutionThreshold,
227+
Duration failureThresholdingPeriod) {
228+
Assert.isTrue(failureRateThreshold >= 1 && failureRateThreshold <= 100,
229+
"failureRateThreshold must be between 1 and 100");
230+
assertFailureExecutionThreshold(failureExecutionThreshold);
231+
assertFailureThresholdingPeriod(failureThresholdingPeriod);
232+
config.failureRateThreshold = failureRateThreshold;
233+
config.failureExecutionThreshold = failureExecutionThreshold;
234+
config.failureThresholdingPeriod = failureThresholdingPeriod;
235+
return this;
236+
}
237+
238+
private void assertFailureExecutionThreshold(int failureExecutionThreshold) {
239+
Assert.isTrue(failureExecutionThreshold >= 1, "failureExecutionThreshold must be >= 1");
240+
}
241+
242+
private void assertFailureThresholdingPeriod(Duration failureThresholdingPeriod) {
243+
Assert.notNull(failureThresholdingPeriod, "failureThresholdingPeriod");
244+
Assert.isTrue(failureThresholdingPeriod.toMillis() >= 10, "failureThresholdingPeriod must be >= 10 ms");
245+
}
246+
247+
/**
248+
* Configures count based success thresholding by setting the number of consecutive successful executions that must
249+
* occur when in a HALF_OPEN state in order to close the circuit, else the circuit is re-opened when a failure
250+
* occurs.
251+
*
252+
* @param successThreshold The number of consecutive successful executions that must occur in order to open the
253+
* circuit
254+
* @throws IllegalArgumentException if {@code successThreshold} < 1
255+
* @see CircuitBreakerConfig#getSuccessThreshold()
256+
*/
257+
public CircuitBreakerBuilder<R> withSuccessThreshold(int successThreshold) {
258+
return withSuccessThreshold(successThreshold, successThreshold);
259+
}
260+
261+
/**
262+
* Configures count based success thresholding by setting the ratio of successive successful executions that must
263+
* occur when in a HALF_OPEN state in order to close the circuit. For example: 5, 10 would close the circuit if 5 out
264+
* of the last 10 executions were successful.
265+
*
266+
* @param successThreshold The number of successful executions that must occur in order to open the circuit
267+
* @param successThresholdingCapacity The capacity for storing execution results when performing success thresholding
268+
* @throws IllegalArgumentException if {@code successThreshold} < 1, {@code successThresholdingCapacity} < 1, or
269+
* {@code successThreshold} > {@code successThresholdingCapacity}
270+
* @see CircuitBreakerConfig#getSuccessThreshold()
271+
* @see CircuitBreakerConfig#getSuccessThresholdingCapacity()
272+
*/
273+
public CircuitBreakerBuilder<R> withSuccessThreshold(int successThreshold, int successThresholdingCapacity) {
274+
Assert.isTrue(successThreshold >= 1, "successThreshold must be >= 1");
275+
Assert.isTrue(successThresholdingCapacity >= 1, "successThresholdingCapacity must be >= 1");
276+
Assert.isTrue(successThresholdingCapacity >= successThreshold,
277+
"successThresholdingCapacity must be >= successThreshold");
278+
config.successThreshold = successThreshold;
279+
config.successThresholdingCapacity = successThresholdingCapacity;
280+
return this;
281+
}
282+
}

0 commit comments

Comments
 (0)