Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.testifyproject.failsafe.CircuitBreaker Maven / Gradle / Ivy
Go to download
A module that provides core Testify SPI and implementation classes
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package org.testifyproject.failsafe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.testifyproject.failsafe.function.BiPredicate;
import org.testifyproject.failsafe.function.CheckedRunnable;
import org.testifyproject.failsafe.function.Predicate;
import org.testifyproject.failsafe.internal.CircuitBreakerStats;
import org.testifyproject.failsafe.internal.CircuitState;
import org.testifyproject.failsafe.internal.ClosedState;
import org.testifyproject.failsafe.internal.HalfOpenState;
import org.testifyproject.failsafe.internal.OpenState;
import org.testifyproject.failsafe.internal.util.Assert;
import org.testifyproject.failsafe.util.Duration;
import org.testifyproject.failsafe.util.Ratio;
/**
* A circuit breaker that temporarily halts execution when configurable thresholds are exceeded.
*
* @author Jonathan Halterman
*/
public class CircuitBreaker {
/** Writes guarded by "this" */
private final AtomicReference state = new AtomicReference();
private final AtomicInteger currentExecutions = new AtomicInteger();
private final CircuitBreakerStats stats = new CircuitBreakerStats() {
@Override
public int getCurrentExecutions() {
return currentExecutions.get();
}
};
private Duration delay = Duration.NONE;
private Duration timeout;
private Ratio failureThreshold;
private Ratio successThreshold;
/** Indicates whether failures are checked by a configured failure condition */
private boolean failuresChecked;
private List> failureConditions;
CheckedRunnable onOpen;
CheckedRunnable onHalfOpen;
CheckedRunnable onClose;
/**
* Creates a Circuit that opens after a single failure, closes after a single success, and has no delay by default.
*/
public CircuitBreaker() {
failureConditions = new ArrayList>();
state.set(new ClosedState(this));
}
/**
* The state of the circuit.
*/
public enum State {
/* The circuit is closed and fully functional, allowing executions to occur. */
CLOSED,
/* The circuit is opened and not allowing executions to occur. */
OPEN,
/* The circuit is temporarily allowing executions to occur. */
HALF_OPEN;
}
/**
* Returns whether the circuit allows execution, possibly triggering a state transition.
*/
public boolean allowsExecution() {
return state.get().allowsExecution(stats);
}
/**
* Closes the circuit.
*/
public void close() {
transitionTo(State.CLOSED, onClose);
}
/**
* Specifies that a failure should be recorded if the {@code completionPredicate} matches the completion result.
*
* @throws NullPointerException if {@code completionPredicate} is null
*/
@SuppressWarnings("unchecked")
public CircuitBreaker failIf(BiPredicate completionPredicate) {
Assert.notNull(completionPredicate, "completionPredicate");
failuresChecked = true;
failureConditions.add((BiPredicate) completionPredicate);
return this;
}
/**
* Specifies that a failure should be recorded if the {@code resultPredicate} matches the result.
*
* @throws NullPointerException if {@code resultPredicate} is null
*/
public CircuitBreaker failIf(Predicate resultPredicate) {
Assert.notNull(resultPredicate, "resultPredicate");
failureConditions.add(Predicates.resultPredicateFor(resultPredicate));
return this;
}
/**
* Specifies the type to fail on. Applies to any type that is assignable from the {@code failure}.
*
* @throws NullPointerException if {@code failure} is null
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public CircuitBreaker failOn(Class extends Throwable> failure) {
Assert.notNull(failure, "failure");
return failOn((List)Arrays.asList(failure));
}
/**
* Specifies the types to fail on. Applies to any type that is assignable from the {@code failures}.
*
* @throws NullPointerException if {@code failures} is null
* @throws IllegalArgumentException if failures is empty
*/
@SuppressWarnings("unchecked")
public CircuitBreaker failOn(Class extends Throwable>... failures) {
Assert.notNull(failures, "failures");
Assert.isTrue(failures.length > 0, "failures cannot be empty");
return failOn(Arrays.asList(failures));
}
/**
* Specifies the types to fail on. Applies to any type that is assignable from the {@code failures}.
*
* @throws NullPointerException if {@code failures} is null
* @throws IllegalArgumentException if failures is empty
*/
public CircuitBreaker failOn(List> failures) {
Assert.notNull(failures, "failures");
Assert.isTrue(!failures.isEmpty(), "failures cannot be empty");
failuresChecked = true;
failureConditions.add(Predicates.failurePredicateFor(failures));
return this;
}
/**
* Specifies that a failure should be recorded if the {@code failurePredicate} matches the failure.
*
* @throws NullPointerException if {@code failurePredicate} is null
*/
public CircuitBreaker failOn(Predicate extends Throwable> failurePredicate) {
Assert.notNull(failurePredicate, "failurePredicate");
failuresChecked = true;
failureConditions.add(Predicates.failurePredicateFor(failurePredicate));
return this;
}
/**
* Specifies that a failure should be recorded if the execution result matches the {@code result}.
*/
public CircuitBreaker failWhen(Object result) {
failureConditions.add(Predicates.resultPredicateFor(result));
return this;
}
/**
* Returns the delay before allowing another execution on the circuit. Defaults to {@link Duration#NONE}.
*
* @see #withDelay(long, TimeUnit)
*/
public Duration getDelay() {
return delay;
}
/**
* Gets the ratio of successive failures that must occur when in a closed state in order to open the circuit else
* {@code null} if none has been configured.
*
* @see #withFailureThreshold(int)
* @see #withFailureThreshold(int, int)
*/
public Ratio getFailureThreshold() {
return failureThreshold;
}
/**
* Gets the state of the circuit.
*/
public State getState() {
return state.get().getState();
}
/**
* Gets the ratio of successive successful executions that must occur when in a half-open state in order to close the
* circuit else {@code null} if none has been configured.
*
* @see #withSuccessThreshold(int)
* @see #withSuccessThreshold(int, int)
*/
public Ratio getSuccessThreshold() {
return successThreshold;
}
/**
* Returns timeout for executions else {@code null} if none has been configured.
*
* @see #withTimeout(long, TimeUnit)
*/
public Duration getTimeout() {
return timeout;
}
/**
* Half-opens the circuit.
*/
public void halfOpen() {
transitionTo(State.HALF_OPEN, onHalfOpen);
}
/**
* Returns whether the circuit is closed.
*/
public boolean isClosed() {
return State.CLOSED.equals(getState());
}
/**
* Returns whether the circuit breaker considers the {@code result} or {@code throwable} a failure based on the
* configured conditions, or if {@code failure} is not null it is not checked by any configured condition.
*
* @see #failIf(BiPredicate)
* @see #failIf(Predicate)
* @see #failOn(Class...)
* @see #failOn(List)
* @see #failOn(Predicate)
* @see #failWhen(Object)
*/
public boolean isFailure(Object result, Throwable failure) {
for (BiPredicate predicate : failureConditions) {
if (predicate.test(result, failure))
return true;
}
// Return true if the failure is not checked by a configured condition
return failure != null && !failuresChecked;
}
/**
* Returns whether the circuit is half open.
*/
public boolean isHalfOpen() {
return State.HALF_OPEN.equals(getState());
}
/**
* Returns whether the circuit is open.
*/
public boolean isOpen() {
return State.OPEN.equals(getState());
}
/**
* Calls the {@code runnable} when the circuit is closed.
*/
public CircuitBreaker onClose(CheckedRunnable runnable) {
onClose = runnable;
return this;
}
/**
* Calls the {@code runnable} when the circuit is half-opened.
*/
public CircuitBreaker onHalfOpen(CheckedRunnable runnable) {
onHalfOpen = runnable;
return this;
}
/**
* Calls the {@code runnable} when the circuit is opened.
*/
public CircuitBreaker onOpen(CheckedRunnable runnable) {
onOpen = runnable;
return this;
}
/**
* Opens the circuit.
*/
public void open() {
transitionTo(State.OPEN, onOpen);
}
/**
* Records an execution {@code failure} as a success or failure based on the failure configuration as determined by
* {@link #isFailure(Object, Throwable)}.
*
* @see #isFailure(Object, Throwable)
*/
public void recordFailure(Throwable failure) {
recordResult(null, failure);
}
/**
* Records an execution {@code result} as a success or failure based on the failure configuration as determined by
* {@link #isFailure(Object, Throwable)}.
*
* @see #isFailure(Object, Throwable)
*/
public void recordResult(Object result) {
recordResult(result, null);
}
/**
* Records an execution success.
*/
public void recordSuccess() {
try {
state.get().recordSuccess();
} finally {
currentExecutions.decrementAndGet();
}
}
@Override
public String toString() {
return getState().toString();
}
/**
* Sets the {@code delay} to wait in open state before transitioning to half-open.
*
* @throws NullPointerException if {@code timeUnit} is null
* @throws IllegalArgumentException if {@code delay} <= 0
*/
public CircuitBreaker withDelay(long delay, TimeUnit timeUnit) {
Assert.notNull(timeUnit, "timeUnit");
Assert.isTrue(delay > 0, "delay must be greater than 0");
this.delay = new Duration(delay, timeUnit);
return this;
}
/**
* Sets the number of successive failures that must occur when in a closed state in order to open the circuit.
*
* @throws IllegalArgumentException if {@code failureThresh} < 1
*/
public CircuitBreaker withFailureThreshold(int failureThreshold) {
Assert.isTrue(failureThreshold >= 1, "failureThreshold must be greater than or equal to 1");
return withFailureThreshold(failureThreshold, failureThreshold);
}
/**
* Sets the ratio of successive failures that must occur when in a closed state in order to open the circuit. For
* example: 5, 10 would open the circuit if 5 out of the last 10 executions result in a failure. The circuit will not
* be opened until at least the given number of {@code executions} have taken place.
*
* @param failures The number of failures that must occur in order to open the circuit
* @param executions The number of executions to measure the {@code failures} against
* @throws IllegalArgumentException if {@code failures} < 1, {@code executions} < 1, or {@code failures} is <
* {@code executions}
*/
public synchronized CircuitBreaker withFailureThreshold(int failures, int executions) {
Assert.isTrue(failures >= 1, "failures must be greater than or equal to 1");
Assert.isTrue(executions >= 1, "executions must be greater than or equal to 1");
Assert.isTrue(executions >= failures, "executions must be greater than or equal to failures");
this.failureThreshold = new Ratio(failures, executions);
state.get().setFailureThreshold(failureThreshold);
return this;
}
/**
* Sets the number of successive successful executions that must occur when in a half-open state in order to close the
* circuit, else the circuit is re-opened when a failure occurs.
*
* @throws IllegalArgumentException if {@code successThreshold} < 1
*/
public CircuitBreaker withSuccessThreshold(int successThreshold) {
Assert.isTrue(successThreshold >= 1, "successThreshold must be greater than or equal to 1");
return withSuccessThreshold(successThreshold, successThreshold);
}
/**
* Sets the ratio of successive successful executions that must occur when in a half-open state in order to close the
* circuit. For example: 5, 10 would close the circuit if 5 out of the last 10 executions were successful. The circuit
* will not be closed until at least the given number of {@code executions} have taken place.
*
* @param successes The number of successful executions that must occur in order to open the circuit
* @param executions The number of executions to measure the {@code successes} against
* @throws IllegalArgumentException if {@code successes} < 1, {@code executions} < 1, or {@code successes} is <
* {@code executions}
*/
public synchronized CircuitBreaker withSuccessThreshold(int successes, int executions) {
Assert.isTrue(successes >= 1, "successes must be greater than or equal to 1");
Assert.isTrue(executions >= 1, "executions must be greater than or equal to 1");
Assert.isTrue(executions >= successes, "executions must be greater than or equal to successes");
this.successThreshold = new Ratio(successes, executions);
state.get().setSuccessThreshold(successThreshold);
return this;
}
/**
* Sets the {@code timeout} for executions. Executions that exceed this timeout are not interrupted, but are recorded
* as failures once they naturally complete.
*
* @throws NullPointerException if {@code timeUnit} is null
* @throws IllegalArgumentException if {@code timeout} <= 0
*/
public CircuitBreaker withTimeout(long timeout, TimeUnit timeUnit) {
Assert.notNull(timeUnit, "timeUnit");
Assert.isTrue(timeout > 0, "timeout must be greater than 0");
this.timeout = new Duration(timeout, timeUnit);
return this;
}
void before() {
currentExecutions.incrementAndGet();
}
/**
* Records an execution failure.
*/
void recordFailure() {
try {
state.get().recordFailure();
} finally {
currentExecutions.decrementAndGet();
}
}
void recordResult(Object result, Throwable failure) {
try {
if (isFailure(result, failure))
state.get().recordFailure();
else
state.get().recordSuccess();
} finally {
currentExecutions.decrementAndGet();
}
}
/**
* Transitions to the {@code newState} if not already in that state and calls any associated event listener.
*/
private void transitionTo(State newState, CheckedRunnable listener) {
boolean transitioned = false;
synchronized (this) {
if (!getState().equals(newState)) {
switch (newState) {
case CLOSED:
state.set(new ClosedState(this));
break;
case OPEN:
state.set(new OpenState(this));
break;
case HALF_OPEN:
state.set(new HalfOpenState(this));
break;
}
transitioned = true;
}
}
if (transitioned && listener != null) {
try {
listener.run();
} catch (Exception ignore) {
}
}
}
}