Skip to content

Commit

Permalink
Feature/159 allow circuit breaker disabled forced open (ReactiveX#194)
Browse files Browse the repository at this point in the history
* ReactiveX#186 Feature: circuitBreaker reset

* Revert "ReactiveX#186 Feature: circuitBreaker reset"

This reverts commit 651dec6b176d52f1cdd3bdf32d7da34c81f80e2c.

* ReactiveX#159 added new states, missing tests

* ReactiveX#159 adding states missed add

ReactiveX#159 adding states part 2

* ReactiveX#159 changes in names

Changing method names, removing unnecessary events and listeners

* ReactiveX#159 Testing State, Metrics and Event Publishing. Added mechanism for Event and State publishing check

* ReactiveX#159 reverting gradle files committed as an error

* ReactiveX#159 fixed CR from codacy

* ReactiveX#159 fixed CR duplication from codacy

* ReactiveX#159 started adding some ascii doc, fixed a missing Event in factory

* ReactiveX#159 fixing/cleaning test

* ReactiveX#159 Adding some documentation

* ReactiveX#159 Adding some documentation

* ReactiveX#159 CR changes

* 159 Adding metrics in FORCED_CLOSED state
  • Loading branch information
rLitto authored and RobWin committed Jan 29, 2018
1 parent 0b58985 commit 51e9da0
Show file tree
Hide file tree
Showing 17 changed files with 488 additions and 193 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
.gradle
build
classes
/resilience4j-ratpack/src/test/groovy/io/github/resilience4j/ratpack/annotation/RateLimiterSpec.groovy
*/out
7 changes: 6 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,19 @@ long failedCalls = metrics.getNumberOfFailedCalls();

`CircuitBreaker` example below:

A `CircuitBreakerEvent` can be a state transition, a successful call, a recorded error or an ignored error. All events contains additional information like event creation time and processing duration of the call. If you want to consume events, you have to register an event consumer.
A `CircuitBreakerEvent` can be a state transition, a circuit breaker reset, a successful call, a recorded error or an ignored error. All events contains additional information like event creation time and processing duration of the call. If you want to consume events, you have to register an event consumer.

[source,java]
----
circuitBreaker.getEventPublisher()
.onSuccess(event -> logger.info(...))
.onError(event -> logger.info(...))
.onIgnoredError(event -> logger.info(...))
.onReset(event -> logger.info(...))
.onStateTransition(event -> logger.info(...));
// Or if you want to register a consumer listening to all events, you can do:
circuitBreaker.getEventPublisher()
.onEvent(event -> logger.info(...));
----

You could use the `CircularEventConsumer` to store events in a circular buffer with a fixed capacity.
Expand Down
2 changes: 1 addition & 1 deletion RELEASENOTES.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Detailed https://github.com/resilience4j/resilience4j/milestone/6?closed=1[PR li
* Issue #126: Created Ratpack CircuitBreaker, RateLimiter sse event streams.
* Issue #139: Support CircuitBreaker failure rate threshold < 1.

NOTE: Braking changes:
NOTE: Breaking changes:

* Issue #51: Removed RxJava2 dependency to make Resilience4j more lightweight. Added a RxJava2 module.
* Issue #148: Created an EventPublisher which replaces the RxJava Event Stream.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@
*/
package io.github.resilience4j.circuitbreaker;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnCallNotPermittedEvent;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerOnErrorEvent;
Expand All @@ -39,6 +32,18 @@
import io.vavr.CheckedFunction0;
import io.vavr.CheckedFunction1;
import io.vavr.CheckedRunnable;
import io.vavr.Tuple;
import io.vavr.Tuple2;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
* A CircuitBreaker instance is thread-safe can be used to decorate multiple requests.
Expand Down Expand Up @@ -111,6 +116,22 @@ public interface CircuitBreaker {
*/
void transitionToHalfOpenState();

/**
* Transitions the state machine to a DISABLED state, stopping state transition, metrics and event publishing.
*
* Should only be used, when you want to disable the circuit breaker allowing all calls to pass.
* To recover from this state you must force a new state transition
*/
void transitionToDisabledState();

/**
* Transitions the state machine to a FORCED_OPEN state, stopping state transition, metrics and event publishing.
*
* Should only be used, when you want to disable the circuit breaker allowing no call to pass.
* To recover from this state you must force a new state transition
*/
void transitionToForcedOpenState();

/**
* Returns the name of this CircuitBreaker.
*
Expand Down Expand Up @@ -194,17 +215,24 @@ default <T> CompletionStage<T> executeCompletionStage(Supplier<CompletionStage<T
* States of the CircuitBreaker state machine.
*/
enum State {
/** A DISABLED breaker is not operating (no state transition, no events)
and allowing all requests through. */
DISABLED(3, false),
/** A CLOSED breaker is operating normally and allowing
requests through. */
CLOSED(0),
CLOSED(0, true),
/** An OPEN breaker has tripped and will not allow requests
through. */
OPEN(1),
OPEN(1, true),
/** A FORCED_OPEN breaker is not operating (no state transition, no events)
and not allowing any requests through. */
FORCED_OPEN(4, false),
/** A HALF_OPEN breaker has completed its wait interval
and will allow requests */
HALF_OPEN(2);
HALF_OPEN(2, true);

private final int order;
public final boolean allowPublish;

/**
* Order is a FIXED integer, it should be preserved regardless of the ordinal number of the enumeration.
Expand All @@ -214,9 +242,11 @@ enum State {
* at 2 and the new state takes 3 regardless of its order in the enum.
*
* @param order
* @param allowPublish
*/
private State(int order){
private State(int order, boolean allowPublish){
this.order = order;
this.allowPublish = allowPublish;
}

public int getOrder(){
Expand All @@ -229,13 +259,46 @@ public int getOrder(){
*/
enum StateTransition {
CLOSED_TO_OPEN(State.CLOSED, State.OPEN),
CLOSED_TO_DISABLED(State.CLOSED, State.DISABLED),
CLOSED_TO_FORCED_OPEN(State.CLOSED, State.FORCED_OPEN),
HALF_OPEN_TO_CLOSED(State.HALF_OPEN, State.CLOSED),
HALF_OPEN_TO_OPEN(State.HALF_OPEN, State.OPEN),
HALF_OPEN_TO_DISABLED(State.HALF_OPEN, State.DISABLED),
HALF_OPEN_TO_FORCED_OPEN(State.HALF_OPEN, State.FORCED_OPEN),
OPEN_TO_CLOSED(State.OPEN, State.CLOSED),
OPEN_TO_HALF_OPEN(State.OPEN, State.HALF_OPEN),
FORCED_OPEN_TO_CLOSED(State.OPEN, State.CLOSED);
OPEN_TO_DISABLED(State.OPEN, State.DISABLED),
OPEN_TO_FORCED_OPEN(State.OPEN, State.FORCED_OPEN),
FORCED_OPEN_TO_CLOSED(State.FORCED_OPEN, State.CLOSED),
FORCED_OPEN_TO_OPEN(State.FORCED_OPEN, State.OPEN),
FORCED_OPEN_TO_DISABLED(State.FORCED_OPEN, State.DISABLED),
FORCED_OPEN_TO_HALF_OPEN(State.FORCED_OPEN, State.HALF_OPEN),
DISABLED_TO_CLOSED(State.DISABLED, State.CLOSED),
DISABLED_TO_OPEN(State.DISABLED, State.OPEN),
DISABLED_TO_FORCED_OPEN(State.DISABLED, State.FORCED_OPEN),
DISABLED_TO_HALF_OPEN(State.DISABLED, State.HALF_OPEN);

private final State fromState;

private final State toState;

private static final Map<Tuple2<State, State>, StateTransition> STATE_TRANSITION_MAP =
Arrays
.stream(StateTransition.values())
.collect(Collectors.toMap(v -> Tuple.of(v.fromState, v.toState), Function.identity()));

private boolean matches(State fromState, State toState) {
return this.fromState == fromState && this.toState == toState;
}

State fromState;
State toState;
public static StateTransition transitionBetween(State fromState, State toState){
final StateTransition stateTransition = STATE_TRANSITION_MAP.get(Tuple.of(fromState, toState));
if(stateTransition == null) {
throw new IllegalStateException(
String.format("Illegal state transition from %s to %s", fromState.toString(), toState.toString()));
}
return stateTransition;
}

StateTransition(State fromState, State toState) {
this.fromState = fromState;
Expand All @@ -250,37 +313,6 @@ public State getToState() {
return toState;
}

public static StateTransition transitionToClosedState(State fromState){
switch (fromState) {
case HALF_OPEN:
return HALF_OPEN_TO_CLOSED;
case OPEN:
return FORCED_OPEN_TO_CLOSED;
default:
throw new IllegalStateException(String.format("Illegal state transition from %s to %s", fromState.toString(), State.CLOSED.toString()));
}
}

public static StateTransition transitionToOpenState(State fromState){
switch (fromState) {
case HALF_OPEN:
return HALF_OPEN_TO_OPEN;
case CLOSED:
return CLOSED_TO_OPEN;
default:
throw new IllegalStateException(String.format("Illegal state transition from %s to %s", fromState.toString(), State.OPEN.toString()));
}
}

public static StateTransition transitionToHalfOpenState(State fromState){
switch (fromState) {
case OPEN:
return OPEN_TO_HALF_OPEN;
default:
throw new IllegalStateException(String.format("Illegal state transition from %s to %s", fromState.toString(), State.HALF_OPEN.toString()));
}
}

@Override
public String toString(){
return String.format("State transition from %s to %s", fromState, toState);
Expand All @@ -298,13 +330,12 @@ interface EventPublisher extends io.github.resilience4j.core.EventPublisher<Circ

EventPublisher onStateTransition(EventConsumer<CircuitBreakerOnStateTransitionEvent> eventConsumer);

EventPublisher onStateReset(EventConsumer<CircuitBreakerOnResetEvent> eventConsumer);
EventPublisher onReset(EventConsumer<CircuitBreakerOnResetEvent> eventConsumer);

EventPublisher onIgnoredError(EventConsumer<CircuitBreakerOnIgnoredErrorEvent> eventConsumer);

EventPublisher onCallNotPermitted(EventConsumer<CircuitBreakerOnCallNotPermittedEvent> eventConsumer);

}
}

interface Metrics {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,26 @@ public interface CircuitBreakerEvent {
*/
enum Type {
/** A CircuitBreakerEvent which informs that an error has been recorded */
ERROR,
ERROR(false),
/** A CircuitBreakerEvent which informs that an error has been ignored */
IGNORED_ERROR,
IGNORED_ERROR(false),
/** A CircuitBreakerEvent which informs that a success has been recorded */
SUCCESS,
SUCCESS(false),
/** A CircuitBreakerEvent which informs that a call was not permitted because the CircuitBreaker state is OPEN */
NOT_PERMITTED,
NOT_PERMITTED(false),
/** A CircuitBreakerEvent which informs the state of the CircuitBreaker has been changed */
STATE_TRANSITION,
STATE_TRANSITION(true),
/** A CircuitBreakerEvent which informs the CircuitBreaker has been reset */
RESET;
RESET(true),
/** A CircuitBreakerEvent which informs the CircuitBreaker has been forced open */
FORCED_OPEN(false),
/** A CircuitBreakerEvent which informs the CircuitBreaker has been disabled */
DISABLED(false);

public final boolean forcePublish;

Type(boolean forcePublish) {
this.forcePublish = forcePublish;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
package io.github.resilience4j.circuitbreaker.internal;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent;

/**
* Abstract state of the CircuitBreaker state machine.
*/
abstract class CircuitBreakerState{

protected CircuitBreakerStateMachine stateMachine;
CircuitBreakerStateMachine stateMachine;

CircuitBreakerState(CircuitBreakerStateMachine stateMachine) {
this.stateMachine = stateMachine;
Expand All @@ -40,4 +41,12 @@ abstract class CircuitBreakerState{
abstract CircuitBreaker.State getState();

abstract CircuitBreakerMetrics getMetrics();

/**
* Should the CircuitBreaker in this state publish events
* @return a boolean signaling if the events should be published
*/
boolean shouldPublishEvents(CircuitBreakerEvent event){
return event.getEventType().forcePublish || getState().allowPublish;
}
}
Loading

0 comments on commit 51e9da0

Please sign in to comment.