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 * @since 3.5
137 */
138 public class EventCountCircuitBreaker extends AbstractCircuitBreaker<Integer> {
139
140 /**
141 * Internally used class for executing check logic based on the current state of the
142 * circuit breaker. Having this logic extracted into special classes avoids complex
143 * if-then-else cascades.
144 */
145 private abstract static class AbstractStateStrategy {
146 /**
147 * Obtains the check interval to applied for the represented state from the given
148 * {@link CircuitBreaker}.
149 *
150 * @param breaker the {@link CircuitBreaker}
151 * @return the check interval to be applied
152 */
153 protected abstract long fetchCheckInterval(EventCountCircuitBreaker breaker);
154
155 /**
156 * Tests whether the end of the current check interval is reached.
157 *
158 * @param breaker the {@link CircuitBreaker}
159 * @param currentData the current state object
160 * @param now the current time
161 * @return a flag whether the end of the current check interval is reached
162 */
163 public boolean isCheckIntervalFinished(final EventCountCircuitBreaker breaker,
164 final CheckIntervalData currentData, final long now) {
165 return now - currentData.getCheckIntervalStart() > fetchCheckInterval(breaker);
166 }
167
168 /**
169 * Tests whether the specified {@link CheckIntervalData} objects indicate that a
170 * state transition should occur. Here the logic which checks for thresholds
171 * depending on the current state is implemented.
172 *
173 * @param breaker the {@link CircuitBreaker}
174 * @param currentData the current {@link CheckIntervalData} object
175 * @param nextData the updated {@link CheckIntervalData} object
176 * @return a flag whether a state transition should be performed
177 */
178 public abstract boolean isStateTransition(EventCountCircuitBreaker breaker,
179 CheckIntervalData currentData, CheckIntervalData nextData);
180 }
181
182 /**
183 * An internally used data class holding information about the checks performed by
184 * this class. Basically, the number of received events and the start time of the
185 * current check interval are stored.
186 */
187 private static final class CheckIntervalData {
188 /** The counter for events. */
189 private final int eventCount;
190
191 /** The start time of the current check interval. */
192 private final long checkIntervalStart;
193
194 /**
195 * Creates a new instance of {@link CheckIntervalData}.
196 *
197 * @param count the current count value
198 * @param intervalStart the start time of the check interval
199 */
200 CheckIntervalData(final int count, final long intervalStart) {
201 eventCount = count;
202 checkIntervalStart = intervalStart;
203 }
204
205 /**
206 * Gets the start time of the current check interval.
207 *
208 * @return the check interval start time
209 */
210 public long getCheckIntervalStart() {
211 return checkIntervalStart;
212 }
213
214 /**
215 * Gets the event counter.
216 *
217 * @return the number of received events
218 */
219 public int getEventCount() {
220 return eventCount;
221 }
222
223 /**
224 * Returns a new instance of {@link CheckIntervalData} with the event counter
225 * incremented by the given delta. If the delta is 0, this object is returned.
226 *
227 * @param delta the delta
228 * @return the updated instance
229 */
230 public CheckIntervalData increment(final int delta) {
231 return delta == 0 ? this : new CheckIntervalData(getEventCount() + delta,
232 getCheckIntervalStart());
233 }
234 }
235
236 /**
237 * A specialized {@link AbstractStateStrategy} implementation for the state closed.
238 */
239 private static final class StateStrategyClosed extends AbstractStateStrategy {
240
241 /**
242 * {@inheritDoc}
243 */
244 @Override
245 protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
246 return breaker.getOpeningInterval();
247 }
248
249 /**
250 * {@inheritDoc}
251 */
252 @Override
253 public boolean isStateTransition(final EventCountCircuitBreaker breaker,
254 final CheckIntervalData currentData, final CheckIntervalData nextData) {
255 return nextData.getEventCount() > breaker.getOpeningThreshold();
256 }
257 }
258
259 /**
260 * A specialized {@link AbstractStateStrategy} implementation for the state open.
261 */
262 private static final class StateStrategyOpen extends AbstractStateStrategy {
263 /**
264 * {@inheritDoc}
265 */
266 @Override
267 protected long fetchCheckInterval(final EventCountCircuitBreaker breaker) {
268 return breaker.getClosingInterval();
269 }
270
271 /**
272 * {@inheritDoc}
273 */
274 @Override
275 public boolean isStateTransition(final EventCountCircuitBreaker breaker,
276 final CheckIntervalData currentData, final CheckIntervalData nextData) {
277 return nextData.getCheckIntervalStart() != currentData
278 .getCheckIntervalStart()
279 && currentData.getEventCount() < breaker.getClosingThreshold();
280 }
281 }
282
283 /** A map for accessing the strategy objects for the different states. */
284 private static final Map<State, AbstractStateStrategy> STRATEGY_MAP = createStrategyMap();
285
286 /**
287 * Creates the map with strategy objects. It allows access for a strategy for a given
288 * state.
289 *
290 * @return the strategy map
291 */
292 private static Map<State, AbstractStateStrategy> createStrategyMap() {
293 final Map<State, AbstractStateStrategy> map = new EnumMap<>(State.class);
294 map.put(State.CLOSED, new StateStrategyClosed());
295 map.put(State.OPEN, new StateStrategyOpen());
296 return map;
297 }
298
299 /**
300 * Returns the {@link AbstractStateStrategy} object responsible for the given state.
301 *
302 * @param state the state
303 * @return the corresponding {@link AbstractStateStrategy}
304 * @throws CircuitBreakingException if the strategy cannot be resolved
305 */
306 private static AbstractStateStrategy stateStrategy(final State state) {
307 return STRATEGY_MAP.get(state);
308 }
309
310 /** Stores information about the current check interval. */
311 private final AtomicReference<CheckIntervalData> checkIntervalData;
312
313 /** The threshold for opening the circuit breaker. */
314 private final int openingThreshold;
315
316 /** The time interval for opening the circuit breaker. */
317 private final long openingInterval;
318
319 /** The threshold for closing the circuit breaker. */
320 private final int closingThreshold;
321
322 /** The time interval for closing the circuit breaker. */
323 private final long closingInterval;
324
325 /**
326 * Creates a new instance of {@link EventCountCircuitBreaker} which uses the same parameters for
327 * opening and closing checks.
328 *
329 * @param threshold the threshold for changing the status of the circuit breaker; if
330 * the number of events received in a check interval is greater than this value, the
331 * circuit breaker is opened; if it is lower than this value, it is closed again
332 * @param checkInterval the check interval for opening or closing the circuit breaker
333 * @param checkUnit the {@link TimeUnit} defining the check interval
334 */
335 public EventCountCircuitBreaker(final int threshold, final long checkInterval, final TimeUnit checkUnit) {
336 this(threshold, checkInterval, checkUnit, threshold);
337 }
338
339 /**
340 * Creates a new instance of {@link EventCountCircuitBreaker} with the same interval for opening
341 * and closing checks.
342 *
343 * @param openingThreshold the threshold for opening the circuit breaker; if this
344 * number of events is received in the time span determined by the check interval, the
345 * circuit breaker is opened
346 * @param checkInterval the check interval for opening or closing the circuit breaker
347 * @param checkUnit the {@link TimeUnit} defining the check interval
348 * @param closingThreshold the threshold for closing the circuit breaker; if the
349 * number of events received in the time span determined by the check interval goes
350 * below this threshold, the circuit breaker is closed again
351 */
352 public EventCountCircuitBreaker(final int openingThreshold, final long checkInterval, final TimeUnit checkUnit,
353 final int closingThreshold) {
354 this(openingThreshold, checkInterval, checkUnit, closingThreshold, checkInterval,
355 checkUnit);
356 }
357
358 /**
359 * Creates a new instance of {@link EventCountCircuitBreaker} and initializes all properties for
360 * opening and closing it based on threshold values for events occurring in specific
361 * intervals.
362 *
363 * @param openingThreshold the threshold for opening the circuit breaker; if this
364 * number of events is received in the time span determined by the opening interval,
365 * the circuit breaker is opened
366 * @param openingInterval the interval for opening the circuit breaker
367 * @param openingUnit the {@link TimeUnit} defining the opening interval
368 * @param closingThreshold the threshold for closing the circuit breaker; if the
369 * number of events received in the time span determined by the closing interval goes
370 * below this threshold, the circuit breaker is closed again
371 * @param closingInterval the interval for closing the circuit breaker
372 * @param closingUnit the {@link TimeUnit} defining the closing interval
373 */
374 public EventCountCircuitBreaker(final int openingThreshold, final long openingInterval,
375 final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
376 final TimeUnit closingUnit) {
377 checkIntervalData = new AtomicReference<>(new CheckIntervalData(0, 0));
378 this.openingThreshold = openingThreshold;
379 this.openingInterval = openingUnit.toNanos(openingInterval);
380 this.closingThreshold = closingThreshold;
381 this.closingInterval = closingUnit.toNanos(closingInterval);
382 }
383
384 /**
385 * Changes the state of this circuit breaker and also initializes a new
386 * {@link CheckIntervalData} object.
387 *
388 * @param newState the new state to be set
389 */
390 private void changeStateAndStartNewCheckInterval(final State newState) {
391 changeState(newState);
392 checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
393 }
394
395 /**
396 * {@inheritDoc}
397 * <p>
398 * This implementation checks the internal event counter against the
399 * threshold values and the check intervals. This may cause a state change of this
400 * circuit breaker.
401 * </p>
402 */
403 @Override
404 public boolean checkState() {
405 return performStateCheck(0);
406 }
407
408 /**
409 * {@inheritDoc}
410 * <p>
411 * A new check interval is started. If too many events are received in
412 * this interval, the circuit breaker changes again to state open. If this circuit
413 * breaker is already closed, this method has no effect, except that a new check
414 * interval is started.
415 * </p>
416 */
417 @Override
418 public void close() {
419 super.close();
420 checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
421 }
422
423 /**
424 * Gets the interval (in nanoseconds) for checking for the closing threshold.
425 *
426 * @return the opening check interval
427 */
428 public long getClosingInterval() {
429 return closingInterval;
430 }
431
432 /**
433 * Gets the threshold value for closing the circuit breaker. If the number of
434 * events received in the time span determined by the closing interval goes below this
435 * threshold, the circuit breaker is closed again.
436 *
437 * @return the closing threshold
438 */
439 public int getClosingThreshold() {
440 return closingThreshold;
441 }
442
443 /**
444 * Gets the interval (in nanoseconds) for checking for the opening threshold.
445 *
446 * @return the opening check interval
447 */
448 public long getOpeningInterval() {
449 return openingInterval;
450 }
451
452 /**
453 * Gets the threshold value for opening the circuit breaker. If this number of
454 * events is received in the time span determined by the opening interval, the circuit
455 * breaker is opened.
456 *
457 * @return the opening threshold
458 */
459 public int getOpeningThreshold() {
460 return openingThreshold;
461 }
462
463 /**
464 * Increments the monitored value by <strong>1</strong> and performs a check of the current state of this
465 * circuit breaker. This method works like {@link #checkState()}, but the monitored
466 * value is incremented before the state check is performed.
467 *
468 * @return <strong>true</strong> if the circuit breaker is now closed;
469 * <strong>false</strong> otherwise
470 */
471 public boolean incrementAndCheckState() {
472 return incrementAndCheckState(1);
473 }
474
475 /**
476 * {@inheritDoc}
477 */
478 @Override
479 public boolean incrementAndCheckState(final Integer increment) {
480 return performStateCheck(increment);
481 }
482
483 /**
484 * Returns the current time in nanoseconds. This method is used to obtain the current
485 * time. This is needed to calculate the check intervals correctly.
486 *
487 * @return the current time in nanoseconds
488 */
489 long nanoTime() {
490 return System.nanoTime();
491 }
492
493 /**
494 * Calculates the next {@link CheckIntervalData} object based on the current data and
495 * the current state. The next data object takes the counter increment and the current
496 * time into account.
497 *
498 * @param increment the increment for the internal counter
499 * @param currentData the current check data object
500 * @param currentState the current state of the circuit breaker
501 * @param time the current time
502 * @return the updated {@link CheckIntervalData} object
503 */
504 private CheckIntervalData nextCheckIntervalData(final int increment,
505 final CheckIntervalData currentData, final State currentState, final long time) {
506 final CheckIntervalData nextData;
507 if (stateStrategy(currentState).isCheckIntervalFinished(this, currentData, time)) {
508 nextData = new CheckIntervalData(increment, time);
509 } else {
510 nextData = currentData.increment(increment);
511 }
512 return nextData;
513 }
514
515 /**
516 * {@inheritDoc}
517 * <p>
518 * This circuit breaker may close itself again if the number of events
519 * received during a check interval goes below the closing threshold. If this circuit
520 * breaker is already open, this method has no effect, except that a new check
521 * interval is started.
522 * </p>
523 */
524 @Override
525 public void open() {
526 super.open();
527 checkIntervalData.set(new CheckIntervalData(0, nanoTime()));
528 }
529
530 /**
531 * Actually checks the state of this circuit breaker and executes a state transition
532 * if necessary.
533 *
534 * @param increment the increment for the internal counter
535 * @return a flag whether the circuit breaker is now closed
536 */
537 private boolean performStateCheck(final int increment) {
538 CheckIntervalData currentData;
539 CheckIntervalData nextData;
540 State currentState;
541
542 do {
543 final long time = nanoTime();
544 currentState = state.get();
545 currentData = checkIntervalData.get();
546 nextData = nextCheckIntervalData(increment, currentData, currentState, time);
547 } while (!updateCheckIntervalData(currentData, nextData));
548
549 // This might cause a race condition if other changes happen in between!
550 // Refer to the header comment!
551 if (stateStrategy(currentState).isStateTransition(this, currentData, nextData)) {
552 currentState = currentState.oppositeState();
553 changeStateAndStartNewCheckInterval(currentState);
554 }
555 return !isOpen(currentState);
556 }
557
558 /**
559 * Updates the {@link CheckIntervalData} object. The current data object is replaced
560 * by the one modified by the last check. The return value indicates whether this was
561 * successful. If it is <strong>false</strong>, another thread interfered, and the
562 * whole operation has to be redone.
563 *
564 * @param currentData the current check data object
565 * @param nextData the replacing check data object
566 * @return a flag whether the update was successful
567 */
568 private boolean updateCheckIntervalData(final CheckIntervalData currentData,
569 final CheckIntervalData nextData) {
570 return currentData == nextData
571 || checkIntervalData.compareAndSet(currentData, nextData);
572 }
573
574 }