/*
*
* Copyright 2016 Robert Winkler
*
* 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 io.github.resilience4j.circuitbreaker.internal;
import static io.github.resilience4j.circuitbreaker.CircuitBreaker.State.CLOSED;
import static io.github.resilience4j.circuitbreaker.CircuitBreaker.State.HALF_OPEN;
import static io.github.resilience4j.circuitbreaker.CircuitBreaker.State.OPEN;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnCallNotPermittedEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnErrorEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnIgnoredErrorEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnStateTransitionEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnSuccessEvent;
import io.reactivex.Flowable;
import io.reactivex.processors.FlowableProcessor;
import io.reactivex.processors.PublishProcessor;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
/**
* A CircuitBreaker finite state machine.
*/
public final class CircuitBreakerStateMachine implements CircuitBreaker {
private static final Logger LOG = LoggerFactory.getLogger(CircuitBreakerStateMachine.class);
private final String name;
private final AtomicReference<CircuitBreakerState> stateReference;
private final CircuitBreakerConfig circuitBreakerConfig;
private final FlowableProcessor<CircuitBreakerEvent> eventPublisher;
/**
* Creates a circuitBreaker.
*
* @param name the name of the CircuitBreaker
* @param circuitBreakerConfig The CircuitBreaker configuration.
*/
public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig) {
this.name = name;
this.circuitBreakerConfig = circuitBreakerConfig;
this.stateReference = new AtomicReference<>(new ClosedState(this));
PublishProcessor<CircuitBreakerEvent> publisher = PublishProcessor.create();
this.eventPublisher = publisher.toSerialized();
}
/**
* Creates a circuitBreaker with default config.
*
* @param name the name of the CircuitBreaker
*/
public CircuitBreakerStateMachine(String name) {
this(name, CircuitBreakerConfig.ofDefaults());
}
/**
* Creates a circuitBreaker.
*
* @param name the name of the CircuitBreaker
* @param circuitBreakerConfig The CircuitBreaker configuration supplier.
*/
public CircuitBreakerStateMachine(String name, Supplier<CircuitBreakerConfig> circuitBreakerConfig) {
this(name, circuitBreakerConfig.get());
}
/**
* Requests permission to call this backend.
*
* @return true, if the call is allowed.
*/
@Override
public boolean isCallPermitted() {
boolean callPermitted = stateReference.get().isCallPermitted();
if (!callPermitted) {
publishCallNotPermittedEvent();
}
return callPermitted;
}
@Override
public void onError(long durationInNanos, Throwable throwable) {
if (circuitBreakerConfig.getRecordFailurePredicate().test(throwable)) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("CircuitBreaker '%s' recorded a failure:", name), throwable);
}
publishCircuitErrorEvent(name, durationInNanos, throwable);
stateReference.get().onError(throwable);
} else {
publishCircuitIgnoredErrorEvent(name, durationInNanos, throwable);
}
}
@Override
public void onSuccess(long durationInNanos) {
publishSuccessEvent(durationInNanos);
stateReference.get().onSuccess();
}
/**
* Get the state of this CircuitBreaker.
*
* @return the the state of this CircuitBreaker
*/
@Override
public State getState() {
return this.stateReference.get().getState();
}
/**
* Get the name of this CircuitBreaker.
*
* @return the the name of this CircuitBreaker
*/
@Override
public String getName() {
return this.name;
}
/**
* Get the config of this CircuitBreaker.
*
* @return the config of this CircuitBreaker
*/
@Override
public CircuitBreakerConfig getCircuitBreakerConfig() {
return circuitBreakerConfig;
}
@Override
public Metrics getMetrics() {
return this.stateReference.get().getMetrics();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return String.format("CircuitBreaker '%s'", this.name);
}
@Override
public void transitionToClosedState() {
CircuitBreakerState previousState = stateReference.getAndUpdate(currentState -> {
if (currentState.getState() == CLOSED) {
return currentState;
}
return new ClosedState(this, currentState.getMetrics());
});
if (previousState.getState() != CLOSED) {
publishStateTransitionEvent(StateTransition.transitionToClosedState(previousState.getState()));
}
}
@Override
public void transitionToOpenState() {
CircuitBreakerState previousState = stateReference.getAndUpdate(currentState -> {
if (currentState.getState() == OPEN) {
return currentState;
}
return new OpenState(this, currentState.getMetrics());
});
if (previousState.getState() != OPEN) {
publishStateTransitionEvent(StateTransition.transitionToOpenState(previousState.getState()));
}
}
@Override
public void transitionToHalfOpenState() {
CircuitBreakerState previousState = stateReference.getAndUpdate(currentState -> {
if (currentState.getState() == HALF_OPEN) {
return currentState;
}
return new HalfOpenState(this);
});
if (previousState.getState() != HALF_OPEN) {
publishStateTransitionEvent(StateTransition.transitionToHalfOpenState(previousState.getState()));
}
}
private void publishStateTransitionEvent(final StateTransition stateTransition) {
if (LOG.isDebugEnabled()) {
LOG.debug(
String.format("CircuitBreaker '%s' changed state from %s to %s",
name, stateTransition.getFromState(), stateTransition.getToState())
);
}
if (eventPublisher.hasSubscribers()) {
eventPublisher.onNext(new CircuitBreakerOnStateTransitionEvent(name, stateTransition));
}
}
private void publishCallNotPermittedEvent() {
if (eventPublisher.hasSubscribers()) {
eventPublisher.onNext(new CircuitBreakerOnCallNotPermittedEvent(name));
}
}
private void publishSuccessEvent(final long durationInNanos) {
if (eventPublisher.hasSubscribers()) {
eventPublisher.onNext(new CircuitBreakerOnSuccessEvent(name, Duration.ofNanos(durationInNanos)));
}
}
private void publishCircuitErrorEvent(final String name, final long durationInNanos, final Throwable throwable) {
if (eventPublisher.hasSubscribers()) {
eventPublisher.onNext(new CircuitBreakerOnErrorEvent(name, Duration.ofNanos(durationInNanos), throwable));
}
}
private void publishCircuitIgnoredErrorEvent(String name, long durationInNanos, Throwable throwable) {
if (eventPublisher.hasSubscribers()) {
eventPublisher.onNext(new CircuitBreakerOnIgnoredErrorEvent(name, Duration.ofNanos(durationInNanos), throwable));
}
}
public Flowable<CircuitBreakerEvent> getEventStream() {
return eventPublisher;
}
}