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.util.concurrent.atomic.AtomicLong;
20
21 /**
22 * A simple implementation of the <a
23 * href="https://martinfowler.com/bliki/CircuitBreaker.html">Circuit Breaker</a> pattern
24 * that opens if the requested increment amount is greater than a given threshold.
25 *
26 * <p>
27 * It contains an internal counter that starts in zero, and each call increments the counter by a given amount.
28 * If the threshold is zero, the circuit breaker will be in a permanent <em>open</em> state.
29 * </p>
30 *
31 * <p>
32 * An example of use case could be a memory circuit breaker.
33 * </p>
34 *
35 * <pre>
36 * long threshold = 10L;
37 * ThresholdCircuitBreaker breaker = new ThresholdCircuitBreaker(10L);
38 * ...
39 * public void handleRequest(Request request) {
40 * long memoryUsed = estimateMemoryUsage(request);
41 * if (breaker.incrementAndCheckState(memoryUsed)) {
42 * // actually handle this request
43 * } else {
44 * // do something else, e.g. send an error code
45 * }
46 * }
47 * </pre>
48 *
49 * <p>
50 * #Thread safe#
51 * </p>
52 *
53 * @since 3.5
54 */
55 public class ThresholdCircuitBreaker extends AbstractCircuitBreaker<Long> {
56
57 /**
58 * The initial value of the internal counter.
59 */
60 private static final long INITIAL_COUNT = 0L;
61
62 /**
63 * The threshold.
64 */
65 private final long threshold;
66
67 /**
68 * Controls the amount used.
69 */
70 private final AtomicLong used;
71
72 /**
73 * Creates a new instance of {@link ThresholdCircuitBreaker} and initializes the threshold.
74 *
75 * @param threshold The threshold.
76 */
77 public ThresholdCircuitBreaker(final long threshold) {
78 this.used = new AtomicLong(INITIAL_COUNT);
79 this.threshold = threshold;
80 }
81
82 /**
83 * {@inheritDoc}
84 */
85 @Override
86 public boolean checkState() {
87 return !isOpen();
88 }
89
90 /**
91 * {@inheritDoc}
92 *
93 * <p>
94 * Resets the internal counter back to its initial value (zero).
95 * </p>
96 */
97 @Override
98 public void close() {
99 super.close();
100 this.used.set(INITIAL_COUNT);
101 }
102
103 /**
104 * Gets the threshold.
105 *
106 * @return The threshold
107 */
108 public long getThreshold() {
109 return threshold;
110 }
111
112 /**
113 * {@inheritDoc}
114 *
115 * <p>
116 * If the threshold is zero, the circuit breaker will be in a permanent <em>open</em> state.
117 * </p>
118 * <p>
119 * The internal counter is a protective counter and only moves toward the threshold: negative
120 * increments are rejected, and an increment that would overflow {@link Long#MAX_VALUE} saturates
121 * the counter at {@link Long#MAX_VALUE} and opens the circuit breaker instead of silently wrapping
122 * negative (which would disable the trip condition).
123 * </p>
124 *
125 * @throws IllegalArgumentException Thrown if the increment is negative.
126 */
127 @Override
128 public boolean incrementAndCheckState(final Long increment) {
129 if (threshold == 0) {
130 open();
131 }
132 final long delta = increment.longValue();
133 if (delta < 0) {
134 throw new IllegalArgumentException("Increment must not be negative: " + delta);
135 }
136 final long used = this.used.accumulateAndGet(delta, (current, add) -> {
137 final long next = current + add;
138 // Both operands are non-negative, so overflow shows up as a decrease: saturate.
139 return next < current ? Long.MAX_VALUE : next;
140 });
141 if (used > threshold || used == Long.MAX_VALUE) {
142 open();
143 }
144 return checkState();
145 }
146
147 }