diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreaker.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreaker.java index 9b7d20717f..f0d8b18fd0 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreaker.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreaker.java @@ -77,6 +77,13 @@ public interface CircuitBreaker { void onSuccess(long durationInNanos); + /** + * Returns the circuit breaker to its original closed state, losing statistics. + * + * Should only be used, when you want to want to fully reset the circuit breaker without creating a new one. + */ + void reset(); + /** * Transitions the state machine to CLOSED state. * @@ -285,6 +292,8 @@ interface EventPublisher extends io.github.resilience4j.core.EventPublisher eventConsumer); + EventPublisher onStateReset(EventConsumer eventConsumer); + EventPublisher onIgnoredError(EventConsumer eventConsumer); EventPublisher onCallNotPermitted(EventConsumer eventConsumer); diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEvent.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEvent.java index 6814590253..f62e37634e 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEvent.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEvent.java @@ -59,6 +59,8 @@ enum Type { /** A CircuitBreakerEvent which informs that a call was not permitted because the CircuitBreaker state is OPEN */ NOT_PERMITTED, /** A CircuitBreakerEvent which informs the state of the CircuitBreaker has been changed */ - STATE_TRANSITION; + STATE_TRANSITION, + /** A CircuitBreakerEvent which informs the CircuitBreaker has been reset */ + RESET; } } diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerOnResetEvent.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerOnResetEvent.java new file mode 100644 index 0000000000..3c898aa766 --- /dev/null +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerOnResetEvent.java @@ -0,0 +1,42 @@ +/* + * + * 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.event; + +/** + * A CircuitBreakerEvent which informs about a reset. + */ +public class CircuitBreakerOnResetEvent extends AbstractCircuitBreakerEvent{ + + public CircuitBreakerOnResetEvent(String circuitBreakerName) { + super(circuitBreakerName); + } + + @Override + public Type getEventType() { + return Type.RESET; + } + + @Override + public String toString(){ + return String.format("%s: CircuitBreaker '%s' reset", + getCreationTime(), + getCircuitBreakerName()); + + } +} diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java index 81ef410ab5..bc1b2cfc11 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java @@ -110,6 +110,8 @@ public void onSuccess(long durationInNanos) { stateReference.get().onSuccess(); } + + /** * Get the state of this CircuitBreaker. * @@ -154,6 +156,15 @@ public String toString() { return String.format("CircuitBreaker '%s'", this.name); } + @Override + public void reset() { + CircuitBreakerState previousState = stateReference.getAndUpdate(currentState -> new ClosedState(this)); + if (previousState.getState() != CLOSED) { + publishStateTransitionEvent(StateTransition.transitionToClosedState(previousState.getState())); + } + publishResetEvent(); + } + @Override public void transitionToClosedState() { CircuitBreakerState previousState = stateReference.getAndUpdate(currentState -> { @@ -165,7 +176,6 @@ public void transitionToClosedState() { if (previousState.getState() != CLOSED) { publishStateTransitionEvent(StateTransition.transitionToClosedState(previousState.getState())); } - } @Override @@ -207,6 +217,19 @@ private void publishStateTransitionEvent(final StateTransition stateTransition) } + private void publishResetEvent() { + if (LOG.isDebugEnabled()) { + LOG.debug( + String.format("CircuitBreaker '%s' reset ", + name) + ); + } + if(eventProcessor.hasConsumers()){ + eventProcessor.consumeEvent(new CircuitBreakerOnResetEvent(name)); + } + + } + private void publishCallNotPermittedEvent() { if(eventProcessor.hasConsumers()) { eventProcessor.consumeEvent(new CircuitBreakerOnCallNotPermittedEvent(name)); @@ -255,6 +278,12 @@ public EventPublisher onStateTransition(EventConsumer onStateResetEventConsumer) { + registerConsumer(CircuitBreakerOnResetEvent.class, onStateResetEventConsumer); + return this; + } + @Override public EventPublisher onIgnoredError(EventConsumer onIgnoredErrorEventConsumer) { registerConsumer(CircuitBreakerOnIgnoredErrorEvent.class, onIgnoredErrorEventConsumer); diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerEventPublisherTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerEventPublisherTest.java index 3f7ceadf41..1bcf8d8e74 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerEventPublisherTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/CircuitBreakerEventPublisherTest.java @@ -106,7 +106,6 @@ public void shouldConsumeOnStateTransitionEvent() { then(logger).should(times(1)).info("STATE_TRANSITION"); } - @Test public void shouldConsumeCallNotPermittedEvent() { circuitBreaker = CircuitBreaker.of("test", CircuitBreakerConfig.custom() diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEventTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEventTest.java index 8ac744c64d..989ce0d80a 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEventTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/event/CircuitBreakerEventTest.java @@ -63,6 +63,15 @@ public void testCircuitBreakerOnStateTransitionEvent() { assertThat(circuitBreakerEvent.toString()).contains("CircuitBreaker 'test' changed state from CLOSED to OPEN"); } + @Test + public void testCircuitBreakerOnResetEvent() { + CircuitBreakerOnResetEvent circuitBreakerEvent = new CircuitBreakerOnResetEvent("test"); + + assertThat(circuitBreakerEvent.getCircuitBreakerName()).isEqualTo("test"); + assertThat(circuitBreakerEvent.getEventType()).isEqualTo(Type.RESET); + assertThat(circuitBreakerEvent.toString()).contains("CircuitBreaker 'test' reset"); + } + @Test public void testCircuitBreakerOnSuccessEvent() { CircuitBreakerOnSuccessEvent circuitBreakerEvent = new CircuitBreakerOnSuccessEvent("test", Duration.ofSeconds(1)); diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java index 6b5a6b2299..5b0cc59f13 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java @@ -173,5 +173,17 @@ public void testCircuitBreakerStateMachine() throws InterruptedException { assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(3); assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(1); assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); + + circuitBreaker.reset(); // Should create a CircuitBreakerOnSuccessEvent (20) + + // The ring buffer back to initial state + // The state machine transitions back to CLOSED state + assertThat(circuitBreaker.isCallPermitted()).isEqualTo(true); + assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED); // Should create a CircuitBreakerOnStateTransitionEvent (21) + assertThat(circuitBreaker.getMetrics().getFailureRate()).isEqualTo(-1f); + assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(0); + assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(0); + assertThat(circuitBreaker.getMetrics().getNumberOfNotPermittedCalls()).isEqualTo(0); + } } diff --git a/resilience4j-consumer/src/test/java/io/github/resilience4j/consumer/CircularEventConsumerTest.java b/resilience4j-consumer/src/test/java/io/github/resilience4j/consumer/CircularEventConsumerTest.java index 0e9f01bf20..88382bac1f 100644 --- a/resilience4j-consumer/src/test/java/io/github/resilience4j/consumer/CircularEventConsumerTest.java +++ b/resilience4j-consumer/src/test/java/io/github/resilience4j/consumer/CircularEventConsumerTest.java @@ -90,11 +90,18 @@ public void shouldBufferAllEvents() { assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(3); assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(2); + circuitBreaker.reset(); + CircuitBreaker.Metrics resetMetrics = circuitBreaker.getMetrics(); + assertThat(resetMetrics.getNumberOfBufferedCalls()).isEqualTo(0); + assertThat(resetMetrics.getNumberOfFailedCalls()).isEqualTo(0); + //Should store 3 events, because circuit emits 2 error events and one state transition event - assertThat(ringBuffer.getBufferedEvents()).hasSize(5); + assertThat(ringBuffer.getBufferedEvents()).hasSize(7); assertThat(ringBuffer.getBufferedEvents()).extracting("eventType") - .containsExactly(Type.SUCCESS, Type.ERROR, Type.IGNORED_ERROR, Type.ERROR, Type.STATE_TRANSITION); + .containsExactly(Type.SUCCESS, Type.ERROR, Type.IGNORED_ERROR, Type.ERROR, Type.STATE_TRANSITION, Type.STATE_TRANSITION, Type.RESET); //ringBuffer.getBufferedEvents().forEach(event -> LOG.info(event.toString())); + + } @Test diff --git a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOFactory.java b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOFactory.java index d50639a781..a5f6f55b6f 100644 --- a/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOFactory.java +++ b/resilience4j-spring-boot/src/main/java/io/github/resilience4j/circuitbreaker/monitoring/endpoint/CircuitBreakerEventDTOFactory.java @@ -33,6 +33,10 @@ static CircuitBreakerEventDTO createCircuitBreakerEventDTO(CircuitBreakerEvent e CircuitBreakerOnStateTransitionEvent onStateTransitionEvent = (CircuitBreakerOnStateTransitionEvent) event; return newCircuitBreakerEventDTOBuilder(onStateTransitionEvent).setStateTransition(onStateTransitionEvent.getStateTransition()) .build(); + case RESET: + CircuitBreakerOnResetEvent onResetEvent = (CircuitBreakerOnResetEvent) event; + return newCircuitBreakerEventDTOBuilder(onResetEvent) + .build(); case IGNORED_ERROR: CircuitBreakerOnIgnoredErrorEvent onIgnoredErrorEvent = (CircuitBreakerOnIgnoredErrorEvent) event; return newCircuitBreakerEventDTOBuilder(onIgnoredErrorEvent).setThrowable(onIgnoredErrorEvent.getThrowable()) diff --git a/resilience4j-spring-boot/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CircuitBreakerEventEmitterTest.java b/resilience4j-spring-boot/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CircuitBreakerEventEmitterTest.java index 5595b99ee8..8579807275 100644 --- a/resilience4j-spring-boot/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CircuitBreakerEventEmitterTest.java +++ b/resilience4j-spring-boot/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CircuitBreakerEventEmitterTest.java @@ -54,6 +54,9 @@ public void testEmitter() throws IOException { exec(run, 2); exec(ignore, 1); exec(fail, 3); + circuitBreaker.reset(); + exec(run, 2); + circuitBreaker.reset(); sseEmitter.complete(); assert handler.isCompleted; @@ -63,7 +66,7 @@ public void testEmitter() throws IOException { .map(CircuitBreakerEventDTO::getType) .collect(toList()); - then(events).containsExactly(SUCCESS, SUCCESS, IGNORED_ERROR, ERROR, ERROR, STATE_TRANSITION, NOT_PERMITTED); + then(events).containsExactly(SUCCESS, SUCCESS, IGNORED_ERROR, ERROR, ERROR, STATE_TRANSITION, NOT_PERMITTED, STATE_TRANSITION, RESET, SUCCESS, SUCCESS, RESET); } private void exec(Runnable runnable, int times) {