1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://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 package org.apache.commons.lang3.concurrent;
18
19 import java.beans.PropertyChangeListener;
20 import java.util.EnumMap;
21 import java.util.Map;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.atomic.AtomicReference;
24
25 /**
26 * A simple implementation of the <a
27 * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
28 * that counts specific events.
29 *
30 * <p>
31 * A <em>circuit breaker</em> can be used to protect an application against unreliable
32 * services or unexpected load. A newly created {@link EventCountCircuitBreaker} object is
33 * initially in state <em>closed</em> meaning that no problem has been detected. When the
34 * application encounters specific events (like errors or service timeouts), it tells the
35 * circuit breaker to increment an internal counter. If the number of events reported in a
36 * specific time interval exceeds a configurable threshold, the circuit breaker changes
37 * into state <em>open</em>. This means that there is a problem with the associated sub
38 * system; the application should no longer call it, but give it some time to settle down.
39 * The circuit breaker can be configured to switch back to <em>closed</em> state after a
40 * certain time frame if the number of events received goes below a threshold.
41 * </p>
42 * <p>
43 * When a {@link EventCountCircuitBreaker} object is constructed the following parameters
44 * can be provided:
45 * </p>
46 * <ul>
47 * <li>A threshold for the number of events that causes a state transition to
48 * <em>open</em> state. If more events are received in the configured check interval, the
49 * circuit breaker switches to <em>open</em> state.</li>
50 * <li>The interval for checks whether the circuit breaker should open. So it is possible
51 * to specify something like "The circuit breaker should open if more than 10 errors are
52 * encountered in a minute."</li>
53 * <li>The same parameters can be specified for automatically closing the circuit breaker
54 * again, as in "If the number of requests goes down to 100 per minute, the circuit
55 * breaker should close itself again". Depending on the use case, it may make sense to use
56 * a slightly lower threshold for closing the circuit breaker than for opening it to avoid
57 * continuously flipping when the number of events received is close to the threshold.</li>
58 * </ul>
59 * <p>
60 * This class supports the following typical use cases:
61 * </p>
62 * <p>
63 * <strong>Protecting against load peaks</strong>
64 * </p>
65 * <p>
66 * Imagine you have a server which can handle a certain number of requests per minute.
67 * Suddenly, the number of requests increases significantly - maybe because a connected
68 * partner system is going mad or due to a denial of service attack. A
69 * {@link EventCountCircuitBreaker} can be configured to stop the application from
70 * processing requests when a sudden peak load is detected and to start request processing
71 * again when things calm down. The following code fragment shows a typical example of
72 * such a scenario. Here the {@link EventCountCircuitBreaker} allows up to 1000 requests
73 * per minute before it interferes. When the load goes down again to 800 requests per
74 * second it switches back to state <em>closed</em>:
75 * </p>
76 *
77 * <pre>
78 * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(1000, 1, TimeUnit.MINUTE, 800);
79 * ...
80 * public void handleRequest(Request request) {
81 * if (breaker.incrementAndCheckState()) {
82 * // actually handle this request
83 * } else {
84 * // do something else, e.g. send an error code
85 * }
86 * }
87 * </pre>
88 * <p>
89 * <strong>Deal with an unreliable service</strong>
90 * </p>
91 * <p>
92 * In this scenario, an application uses an external service which may fail from time to
93 * time. If there are too many errors, the service is considered down and should not be
94 * called for a while. This can be achieved using the following pattern - in this concrete
95 * example we accept up to 5 errors in 2 minutes; if this limit is reached, the service is
96 * given a rest time of 10 minutes:
97 * </p>
98 *
99 * <pre>
100 * EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(5, 2, TimeUnit.MINUTE, 5, 10, TimeUnit.MINUTE);
101 * ...
102 * public void handleRequest(Request request) {
103 * if (breaker.checkState()) {
104 * try {
105 * service.doSomething();
106 * } catch (ServiceException ex) {
107 * breaker.incrementAndCheckState();
108 * }
109 * } else {
110 * // return an error code, use an alternative service, etc.
111 * }
112 * }
113 * </pre>
114 * <p>
115 * In addition to automatic state transitions, the state of a circuit breaker can be
116 * changed manually using the methods {@link #open()} and {@link #close()}. It is also
117 * possible to register {@link PropertyChangeListener} objects that get notified whenever
118 * a state transition occurs. This is useful, for instance to directly react on a freshly
119 * detected error condition.
120 * </p>
121 * <p>
122 * <em>Implementation notes:</em>
123 * </p>
124 * <ul>
125 * <li>This implementation uses non-blocking algorithms to update the internal counter and
126 * state. This should be pretty efficient if there is not too much contention.</li>
127 * <li>This implementation is not intended to operate as a high-precision timer in very
128 * short check intervals. It is deliberately kept simple to avoid complex and
129 * time-consuming state checks. It should work well in time intervals from a few seconds
130 * up to minutes and longer. If the intervals become too short, there might be race
131 * conditions causing spurious state transitions.</li>
132 * <li>The handling of check intervals is a bit simplistic. Therefore, there is no
133 * guarantee that the circuit breaker is triggered at a specific point in time; there may
134 * be some delay (less than a check interval).</li>
135 * </ul>
136 *
137 * @since 3.5
138 */
139 public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {
140
141 /**
142 * Internally used class for executing check logic based on the current state of the
143 * circuit breaker. Having this logic extracted into special classes avoids complex
144 * if-then-else cascades.
145 */
146 private abstract static class AbstractStateStrategy {
147
148 /**
149 * Obtains the check interval to applied for the represented state from the given
150 * {@link CircuitBreaker}.
151 *
152 * @param breaker the {@link CircuitBreaker}
153 * @return the check interval to be applied
154 */
155 protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker);
156
157 /**
158 * Tests whether the end of the current check interval is reached.
159 *
160 * @param breaker the {@link CircuitBreaker}
161 * @param currentData the current state object
162 * @param now the current time
163 * @return a flag whether the end of the current check interval is reached
164 */
165 public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker,
166 final CheckIntervalData currentData, final long now) {
167 return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker);
168 }
169
170 /**
171 * Tests whether the specified {@link CheckIntervalData} objects indicate that a
172 * state transition should occur. Here the logic which checks for thresholds
173 * depending on the current state is implemented.
174 *
175 * @param breaker the {@link CircuitBreaker}
176 * @param currentData the current {@link CheckIntervalData} object
177 * @param nextData the updated {@link CheckIntervalData} object
178 * @return a flag whether a state transition should be performed
179 */
180 public abstract boolean isStateTransition(EventCountCircuitBreaker breaker,
181 CheckIntervalData currentData, CheckIntervalData nextData);
182 }
183
184 /**
185 * An internally used data class holding information about the checks performed by
186 * this class. Basically, the number of received events and the start time of the
187 * current check interval are stored.
188 */
189 private static final class CheckIntervalData {
190
191 /** The counter for events. */
192 private final int eventCount;
193
194 /** The start time of the current check interval. */
195 private final long checkIntervalStart;
196
197 /**
198 * Creates a new instance of {@link CheckIntervalData}.
199 *
200 * @param count the current count value
201 * @param intervalStart the start time of the check interval
202 */
203 CheckIntervalData(final int count, final long intervalStart) {
204 eventCount = count;
205 checkIntervalStart = intervalStart;
206 }
207
208 /**
209 * Gets the start time of the current check interval.
210 *
211 * @return the check interval start time
212 */
213 public long getCheckIntervalStart() {
214 return checkIntervalStart;
215 }
216
217 /**
218 * Gets the event counter.
219 *
220 * @return the number of received events
221 */
222 public int getEventCount() {
223 return eventCount;
224 }
225
226 /**
227 * Returns a new instance of {@link CheckIntervalData} with the event counter
228 * incremented by the given delta. If the delta is 0, this object is returned.
229 *
230 * @param delta the delta
231 * @return the updated instance
232 */
233 public CheckIntervalData increment(final int delta) {
234 return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta,
235 getCheckIntervalStart());
236 }
237 }
238
239 /**
240 * A specialized {@link AbstractStateStrategy} implementation for the state closed.
241 */
242 private static final class StateStrategyClosed extends AbstractStateStrategy {
243
244 /**
245 * {@inheritDoc}
246 */
247 @Override
248 protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
249 return breaker.getOpeningInterval();
250 }
251
252 /**
253 * {@inheritDoc}
254 */
255 @Override
256 public boolean isStateTransition(final EventCountCircuitBreaker breaker,
257 final CheckIntervalData currentData, final CheckIntervalData nextData) {
258 return nextData.getEventCount() > breaker.getOpeningThreshold();
259 }
260 }
261
262 /**
263 * A specialized {@link AbstractStateStrategy} implementation for the state open.
264 */
265 private static final class StateStrategyOpen extends AbstractStateStrategy {
266
267 /**
268 * {@inheritDoc}
269 */
270 @Override
271 protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
272 return breaker.getClosingInterval();
273 }
274
275 /**
276 * {@inheritDoc}
277 */
278 @Override
279 public boolean isStateTransition(final EventCountCircuitBreaker breaker,
280 final CheckIntervalData currentData, final CheckIntervalData nextData) {
281 return nextData.getCheckIntervalStart() != currentData
282 .getCheckIntervalStart()
283 && currentData.getEventCount() < breaker.getClosingThreshold();
284 }
285 }
286
287 /** A map for accessing the strategy objects for the different states. */
288 private static final Map<State, AbstractStateStrategy> STRATEGY_MAP = createStrategyMap();
289
290 /**
291 * Creates the map with strategy objects. It allows access for a strategy for a given
292 * state.
293 *
294 * @return the strategy map
295 */
296 private static Map<State, AbstractStateStrategy> createStrategyMap() {
297 final Map<State, AbstractStateStrategy> map = new EnumMap<>(State.class);
298 map.put(State.CLOSED, new StateStrategyClosed());
299 map.put(State.OPEN, new StateStrategyOpen());
300 return map;
301 }
302
303 /**
304 * Returns the {@link AbstractStateStrategy} object responsible for the given state.
305 *
306 * @param state the state
307 * @return the corresponding {@link AbstractStateStrategy}
308 * @throws CircuitBreakingException if the strategy cannot be resolved
309 */
310 private static AbstractStateStrategy stateStrategy(final State state) {
311 return STRATEGY_MAP.get(state);
312 }
313
314 /** Stores information about the current check interval. */
315 private final AtomicReference<CheckIntervalData> checkIntervalData;
316
317 /** The threshold for opening the circuit breaker. */
318 private final int openingThreshold;
319
320 /** The time interval for opening the circuit breaker. */
321 private final long openingInterval;
322
323 /** The threshold for closing the circuit breaker. */
324 private final int closingThreshold;
325
326 /** The time interval for closing the circuit breaker. */
327 private final long closingInterval;
328
329 /**
330 * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for
331 * opening and closing checks.
332 *
333 * @param threshold the threshold for changing the status of the circuit breaker; if
334 * the number of events received in a check interval is greater than this value, the
335 * circuit breaker is opened; if it is lower than this value, it is closed again
336 * @param checkInterval the check interval for opening or closing the circuit breaker
337 * @param checkUnit the {@link TimeUnit} defining the check interval
338 */
339 public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) {
340 this(threshold, checkInterval, checkUnit, threshold);
341 }
342
343 /**
344 * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening
345 * and closing checks.
346 *
347 * @param openingThreshold the threshold for opening the circuit breaker; if this
348 * number of events is received in the time span determined by the check interval, the
349 * circuit breaker is opened
350 * @param checkInterval the check interval for opening or closing the circuit breaker
351 * @param checkUnit the {@link TimeUnit} defining the check interval
352 * @param closingThreshold the threshold for closing the circuit breaker; if the
353 * number of events received in the time span determined by the check interval goes
354 * below this threshold, the circuit breaker is closed again
355 */
356 public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit,
357 final int closingThreshold) {
358 this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval,
359 checkUnit);
360 }
361
362 /**
363 * Creates a new instance of {@link EventCountCircuitBreaker} and initializes all properties for
364 * opening and closing it based on threshold values for events occurring in specific
365 * intervals.
366 *
367 * @param openingThreshold the threshold for opening the circuit breaker; if this
368 * number of events is received in the time span determined by the opening interval,
369 * the circuit breaker is opened
370 * @param openingInterval the interval for opening the circuit breaker
371 * @param openingUnit the {@link TimeUnit} defining the opening interval
372 * @param closingThreshold the threshold for closing the circuit breaker; if the
373 * number of events received in the time span determined by the closing interval goes
374 * below this threshold, the circuit breaker is closed again
375 * @param closingInterval the interval for closing the circuit breaker
376 * @param closingUnit the {@link TimeUnit} defining the closing interval
377 */
378 public EventCountCircuitBreaker(final int openingThreshold, final long openingInterval,
379 final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
380 final TimeUnit closingUnit) {
381 checkIntervalData = new AtomicReference<>(new CheckIntervalData(0, 0));
382 this.openingThreshold = openingThreshold;
383 this.openingInterval = openingUnit.toNanos(openingInterval);
384 this.closingThreshold = closingThreshold;
385 this.closingInterval = closingUnit.toNanos(closingInterval);
386 }
387
388 /**
389 * Changes the state of this circuit breaker and also initializes a new
390 * {@link CheckIntervalData} object.
391 *
392 * @param newState the new state to be set
393 */
394 private void changeStateAndStartNewCheckInterval(final State newState) {
395 changeState(newState);
396 checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
397 }
398
399 /**
400 * {@inheritDoc}
401 * <p>
402 * This implementation checks the internal event counter against the
403 * threshold values and the check intervals. This may cause a state change of this
404 * circuit breaker.
405 * </p>
406 */
407 @Override
408 public boolean checkState() {
409 return performStateCheck(0);
410 }
411
412 /**
413 * {@inheritDoc}
414 * <p>
415 * A new check interval is started. If too many events are received in
416 * this interval, the circuit breaker changes again to state open. If this circuit
417 * breaker is already closed, this method has no effect, except that a new check
418 * interval is started.
419 * </p>
420 */
421 @Override
422 public void close() {
423 super.close();
424 checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
425 }
426
427 /**
428 * Gets the interval (in nanoseconds) for checking for the closing threshold.
429 *
430 * @return the opening check interval
431 */
432 public long getClosingInterval() {
433 return closingInterval;
434 }
435
436 /**
437 * Gets the threshold value for closing the circuit breaker. If the number of
438 * events received in the time span determined by the closing interval goes below this
439 * threshold, the circuit breaker is closed again.
440 *
441 * @return the closing threshold
442 */
443 public int getClosingThreshold() {
444 return closingThreshold;
445 }
446
447 /**
448 * Gets the interval (in nanoseconds) for checking for the opening threshold.
449 *
450 * @return the opening check interval
451 */
452 public long getOpeningInterval() {
453 return openingInterval;
454 }
455
456 /**
457 * Gets the threshold value for opening the circuit breaker. If this number of
458 * events is received in the time span determined by the opening interval, the circuit
459 * breaker is opened.
460 *
461 * @return the opening threshold
462 */
463 public int getOpeningThreshold() {
464 return openingThreshold;
465 }
466
467 /**
468 * Increments the monitored value by <strong>1</strong> and performs a check of the current state of this
469 * circuit breaker. This method works like {@link #checkState()}, but the monitored
470 * value is incremented before the state check is performed.
471 *
472 * @return <strong>true</strong> if the circuit breaker is now closed;
473 * <strong>false</strong> otherwise
474 */
475 public boolean incrementAndCheckState() {
476 return incrementAndCheckState(1);
477 }
478
479 /**
480 * {@inheritDoc}
481 */
482 @Override
483 public boolean incrementAndCheckState(final Integer increment) {
484 return performStateCheck(increment);
485 }
486
487 /**
488 * Returns the current time in nanoseconds. This method is used to obtain the current
489 * time. This is needed to calculate the check intervals correctly.
490 *
491 * @return the current time in nanoseconds
492 */
493 long nanoTime() {
494 return System.nanoTime();
495 }
496
497 /**
498 * Calculates the next {@link CheckIntervalData} object based on the current data and
499 * the current state. The next data object takes the counter increment and the current
500 * time into account.
501 *
502 * @param increment the increment for the internal counter
503 * @param currentData the current check data object
504 * @param currentState the current state of the circuit breaker
505 * @param time the current time
506 * @return the updated {@link CheckIntervalData} object
507 */
508 private CheckIntervalData nextCheckIntervalData(final int increment,
509 final CheckIntervalData currentData, final State currentState, final long time) {
510 final CheckIntervalData nextData;
511 if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) {
512 nextData = new CheckIntervalData(increment, time);
513 } else {
514 nextData = currentData.increment(increment);
515 }
516 return nextData;
517 }
518
519 /**
520 * {@inheritDoc}
521 * <p>
522 * This circuit breaker may close itself again if the number of events
523 * received during a check interval goes below the closing threshold. If this circuit
524 * breaker is already open, this method has no effect, except that a new check
525 * interval is started.
526 * </p>
527 */
528 @Override
529 public void open() {
530 super.open();
531 checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
532 }
533
534 /**
535 * Actually checks the state of this circuit breaker and executes a state transition
536 * if necessary.
537 *
538 * @param increment the increment for the internal counter
539 * @return a flag whether the circuit breaker is now closed
540 */
541 private boolean performStateCheck(final int increment) {
542 CheckIntervalData currentData;
543 CheckIntervalData nextData;
544 State currentState;
545
546 do {
547 final long time = nanoTime();
548 currentState = state.get();
549 currentData = checkIntervalData.get();
550 nextData = nextCheckIntervalData(increment, currentData, currentState, time);
551 } while (!updateCheckIntervalData(currentData, nextData));
552
553 // This might cause a race condition if other changes happen in between!
554 // Refer to the header comment!
555 if (stateStrategy(currentState).isStateTransition(this, currentData, nextData)) {
556 currentState = currentState.oppositeState();
557 changeStateAndStartNewCheckInterval(currentState);
558 }
559 return !isOpen(currentState);
560 }
561
562 /**
563 * Updates the {@link CheckIntervalData} object. The current data object is replaced
564 * by the one modified by the last check. The return value indicates whether this was
565 * successful. If it is <strong>false</strong>, another thread interfered, and the
566 * whole operation has to be redone.
567 *
568 * @param currentData the current check data object
569 * @param nextData the replacing check data object
570 * @return a flag whether the update was successful
571 */
572 private boolean updateCheckIntervalData(final CheckIntervalData currentData,
573 final CheckIntervalData nextData) {
574 return currentData == nextData
575 || checkIntervalData.compareAndSet(currentData, nextData);
576 }
577
578 }