Skip to content

Commit 57a41d5

Browse files
author
Robert Winkler
committed
Issue ReactiveX#38: A CircuitBreaker records not permitted calls when the state is OPEN and emits a event for each attempt. The metric "numberOfNotPermittedCalls" can be access via CircuitBreaker.Metrics
1 parent 482add7 commit 57a41d5

File tree

9 files changed

+145
-104
lines changed

9 files changed

+145
-104
lines changed

src/main/java/io/github/robwin/circuitbreaker/CircuitBreaker.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,18 @@ interface Metrics {
225225
/**
226226
* Returns the current number of failed calls.
227227
*
228-
* @return the current number of failed calls.
228+
* @return the current number of failed calls
229229
*/
230230
int getNumberOfFailedCalls();
231231

232+
/**
233+
* Returns the current number of denied calls.
234+
* The number of denied calls is always 0, when the CircuitBreaker state is CLOSED or HALF_OPEN.
235+
* The number of denied calls is only increased when the CircuitBreaker state is OPEN.
236+
*
237+
* @return the current number of denied calls
238+
*/
239+
long getNumberOfNotPermittedCalls();
232240

233241
/**
234242
* Returns the maximum number of buffered calls.
@@ -237,7 +245,6 @@ interface Metrics {
237245
*/
238246
int getMaxNumberOfBufferedCalls();
239247

240-
241248
/**
242249
* Returns the maximum number of successful calls.
243250
*

src/main/java/io/github/robwin/circuitbreaker/event/CircuitBreakerEvent.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ enum Type {
5656
IGNORED_ERROR,
5757
/** A CircuitBreakerEvent which informs that a success has been recorded */
5858
SUCCESS,
59+
/** A CircuitBreakerEvent which informs that a call was not permitted because the CircuitBreaker state is OPEN */
60+
NOT_PERMITTED,
5961
/** A CircuitBreakerEvent which informs the state of the CircuitBreaker has been changed */
60-
STATE_TRANSITION
62+
STATE_TRANSITION;
6163
}
6264
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
*
3+
* Copyright 2016 Robert Winkler
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.robwin.circuitbreaker.event;
20+
21+
/**
22+
* A CircuitBreakerEvent which informs that a call was not permitted, because the CircuitBreaker is OPEN.
23+
*/
24+
public class CircuitBreakerOnCallNotPermittedEvent extends AbstractCircuitBreakerEvent {
25+
26+
public CircuitBreakerOnCallNotPermittedEvent(String circuitBreakerName) {
27+
super(circuitBreakerName);
28+
}
29+
30+
@Override
31+
public Type getEventType() {
32+
return Type.NOT_PERMITTED;
33+
}
34+
35+
@Override
36+
public String toString() {
37+
return String.format("%s: CircuitBreaker '%s' recorded a call which was not permitted.",
38+
getCreationTime(),
39+
getCircuitBreakerName());
40+
}
41+
}

src/main/java/io/github/robwin/circuitbreaker/internal/CircuitBreakerMetrics.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,18 @@
2121

2222
import io.github.robwin.circuitbreaker.CircuitBreaker;
2323

24+
import java.util.concurrent.atomic.LongAdder;
25+
2426
class CircuitBreakerMetrics implements CircuitBreaker.Metrics {
2527

2628
private final int ringBufferSize;
2729
private final RingBitSet ringBitSet;
30+
private final LongAdder numberOfNotPermittedCalls;
2831

2932
CircuitBreakerMetrics(int ringBufferSize) {
3033
this.ringBufferSize = ringBufferSize;
3134
this.ringBitSet = new RingBitSet(this.ringBufferSize);
35+
this.numberOfNotPermittedCalls = new LongAdder();
3236
}
3337

3438
/**
@@ -51,6 +55,12 @@ float onSuccess() {
5155
return getFailureRate(currentNumberOfFailedCalls);
5256
}
5357

58+
/**
59+
* Records a call which was not permitted, because the CircuitBreaker state is OPEN.
60+
*/
61+
void onCallNotPermitted() {
62+
numberOfNotPermittedCalls.increment();
63+
}
5464

5565
/**
5666
* {@inheritDoc}
@@ -84,6 +94,14 @@ public int getNumberOfBufferedCalls() {
8494
return this.ringBitSet.length();
8595
}
8696

97+
/**
98+
* {@inheritDoc}
99+
*/
100+
@Override
101+
public long getNumberOfNotPermittedCalls() {
102+
return this.numberOfNotPermittedCalls.sum();
103+
}
104+
87105
/**
88106
* {@inheritDoc}
89107
*/

src/main/java/io/github/robwin/circuitbreaker/internal/CircuitBreakerStateMachine.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ public CircuitBreakerStateMachine(String name, Supplier<CircuitBreakerConfig> ci
8686
*/
8787
@Override
8888
public boolean isCallPermitted() {
89-
return stateReference.get().isCallPermitted();
89+
boolean callPermitted = stateReference.get().isCallPermitted();
90+
if(!callPermitted){
91+
publishCircuitBreakerEvent(() -> new CircuitBreakerOnCallNotPermittedEvent(getName()));
92+
}
93+
return callPermitted;
9094
}
9195

9296
@Override

src/main/java/io/github/robwin/circuitbreaker/internal/OpenState.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,30 @@ boolean isCallPermitted() {
4646
stateMachine.transitionToHalfOpenState();
4747
return true;
4848
}
49+
circuitBreakerMetrics.onCallNotPermitted();
4950
return false;
5051
}
5152

5253
/**
53-
* Should never be called, because isCallPermitted returns false.
54+
* Should never be called when isCallPermitted returns false.
5455
*/
5556
@Override
5657
void onError(Throwable throwable) {
5758
// Could be called when Thread 1 invokes isCallPermitted when the state is CLOSED, but in the meantime another
58-
// Thread 2 calls onError and the state changes from CLOSED to OPEN before Thread 1 calls onError
59-
// Do nothing, because the original Exception should be thrown.
59+
// Thread 2 calls onError and the state changes from CLOSED to OPEN before Thread 1 calls onError.
60+
// But the onError event should still be recorded, even if it happened after the state transition.
61+
circuitBreakerMetrics.onError();
6062
}
6163

6264
/**
63-
* Should never be called, because isCallPermitted returns false.
65+
* Should never be called when isCallPermitted returns false.
6466
*/
6567
@Override
6668
void onSuccess() {
6769
// Could be called when Thread 1 invokes isCallPermitted when the state is CLOSED, but in the meantime another
68-
// Thread 2 calls onError and the state changes from CLOSED to OPEN before Thread 1 calls onSuccess
69-
// Do nothing, because the call was successful.
70+
// Thread 2 calls onError and the state changes from CLOSED to OPEN before Thread 1 calls onSuccess.
71+
// But the onSuccess event should still be recorded, even if it happened after the state transition.
72+
circuitBreakerMetrics.onSuccess();
7073
}
7174

7275
/**

src/test/java/io/github/robwin/circuitbreaker/concurrent/ConcurrentCircuitBreakerTest.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ public void setUp() {
5454

5555
circuitBreaker = (CircuitBreakerStateMachine) of("testName", circuitBreakerConfig);
5656
errorEventSubscriber = circuitBreaker.getEventStream()
57-
.filter(event -> event.getEventType() == Type.ERROR)
58-
.map(CircuitBreakerEvent::getEventType)
59-
.test();
57+
.filter(event -> event.getEventType() == Type.ERROR)
58+
.map(CircuitBreakerEvent::getEventType)
59+
.test();
6060

6161
stateTransitionSubsriber = circuitBreaker.getEventStream()
62-
.filter(event -> event.getEventType() == Type.STATE_TRANSITION)
63-
.cast(CircuitBreakerOnStateTransitionEvent.class)
64-
.map(CircuitBreakerOnStateTransitionEvent::getStateTransition)
65-
.test();
62+
.filter(event -> event.getEventType() == Type.STATE_TRANSITION)
63+
.cast(CircuitBreakerOnStateTransitionEvent.class)
64+
.map(CircuitBreakerOnStateTransitionEvent::getStateTransition)
65+
.test();
6666
}
6767

6868
@ThreadedMain
@@ -85,6 +85,5 @@ public void arbiter() {
8585
stateTransitionSubsriber
8686
.assertValue(StateTransition.CLOSED_TO_OPEN);
8787

88-
8988
}
9089
}

src/test/java/io/github/robwin/circuitbreaker/internal/CircuitBreakerMetricsTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ public void testDefaultCircuitBreakerMetrics(){
3333
circuitBreakerMetrics.onSuccess();
3434
circuitBreakerMetrics.onError();
3535
circuitBreakerMetrics.onError();
36+
circuitBreakerMetrics.onCallNotPermitted();
37+
circuitBreakerMetrics.onCallNotPermitted();
3638

3739
assertThat(circuitBreakerMetrics.getNumberOfBufferedCalls()).isEqualTo(4);
3840
assertThat(circuitBreakerMetrics.getNumberOfFailedCalls()).isEqualTo(2);
3941
assertThat(circuitBreakerMetrics.getNumberOfSuccessfulCalls()).isEqualTo(2);
42+
assertThat(circuitBreakerMetrics.getNumberOfNotPermittedCalls()).isEqualTo(2);
4043

4144
// The failure rate must be -1, because the number of measured calls is below the buffer size of 10
4245
assertThat(circuitBreakerMetrics.getFailureRate()).isEqualTo(-1);

0 commit comments

Comments
 (0)