1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3.concurrent;
18
19 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNotEquals;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24
25 import java.beans.PropertyChangeEvent;
26 import java.beans.PropertyChangeListener;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.concurrent.CountDownLatch;
30 import java.util.concurrent.TimeUnit;
31
32 import org.apache.commons.lang3.AbstractLangTest;
33 import org.apache.commons.lang3.ArrayUtils;
34 import org.junit.jupiter.api.Test;
35
36
37
38
39 public class EventCountCircuitBreakerTest extends AbstractLangTest {
40
41
42
43 private static final class ChangeListener implements PropertyChangeListener {
44
45 private final Object expectedSource;
46
47
48 private final List<Boolean> changedValues;
49
50
51
52
53
54
55
56 ChangeListener(final Object source) {
57 expectedSource = source;
58 changedValues = new ArrayList<>();
59 }
60
61 @Override
62 public void propertyChange(final PropertyChangeEvent evt) {
63 assertEquals(expectedSource, evt.getSource(), "Wrong event source");
64 assertEquals("open", evt.getPropertyName(), "Wrong property name");
65 final Boolean newValue = (Boolean) evt.getNewValue();
66 final Boolean oldValue = (Boolean) evt.getOldValue();
67 assertNotEquals(newValue, oldValue, "Old and new value are equal");
68 changedValues.add(newValue);
69 }
70
71
72
73
74
75
76 public void verify(final Boolean... values) {
77 assertArrayEquals(values, changedValues.toArray(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY));
78 }
79 }
80
81
82
83
84
85
86 private static final class EventCountCircuitBreakerTestImpl extends EventCountCircuitBreaker {
87
88 private long currentTime;
89
90 EventCountCircuitBreakerTestImpl(final int openingThreshold, final long openingInterval,
91 final TimeUnit openingUnit, final int closingThreshold, final long closingInterval,
92 final TimeUnit closingUnit) {
93 super(openingThreshold, openingInterval, openingUnit, closingThreshold,
94 closingInterval, closingUnit);
95 }
96
97
98
99
100
101
102
103 public EventCountCircuitBreakerTestImpl at(final long time) {
104 currentTime = time;
105 return this;
106 }
107
108
109
110
111
112 @Override
113 long nanoTime() {
114 return currentTime;
115 }
116 }
117
118
119 private static final int OPENING_THRESHOLD = 10;
120
121
122 private static final int CLOSING_THRESHOLD = 5;
123
124
125 private static final long NANO_FACTOR = 1000L * 1000L * 1000L;
126
127
128
129
130
131 @Test
132 public void testAutomaticOpenStartsNewCheckInterval() {
133 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
134 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
135 long time = 10 * NANO_FACTOR;
136 for (int i = 0; i <= OPENING_THRESHOLD; i++) {
137 breaker.at(time++).incrementAndCheckState();
138 }
139 assertTrue(breaker.isOpen(), "Not open");
140 time += NANO_FACTOR - 1000;
141 assertFalse(breaker.at(time).incrementAndCheckState(), "Already closed");
142 time += 1001;
143 assertTrue(breaker.at(time).checkState(), "Not closed in time interval");
144 }
145
146
147
148
149 @Test
150 public void testChangeEvents() {
151 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
152 TimeUnit.SECONDS);
153 final ChangeListener listener = new ChangeListener(breaker);
154 breaker.addChangeListener(listener);
155 breaker.open();
156 breaker.close();
157 listener.verify(Boolean.TRUE, Boolean.FALSE);
158 }
159
160
161
162
163 @Test
164 public void testChangeEventsGeneratedByAutomaticTransitions() {
165 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
166 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
167 final ChangeListener listener = new ChangeListener(breaker);
168 breaker.addChangeListener(listener);
169 long time = 0;
170 for (int i = 0; i <= OPENING_THRESHOLD; i++, time += 1000) {
171 breaker.at(time).incrementAndCheckState();
172 }
173 breaker.at(NANO_FACTOR + 1).checkState();
174 breaker.at(3 * NANO_FACTOR).checkState();
175 listener.verify(Boolean.TRUE, Boolean.FALSE);
176 }
177
178
179
180
181 @Test
182 public void testClose() {
183 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
184 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
185 long time = 0;
186 for (int i = 0; i <= OPENING_THRESHOLD; i++, time += 1000) {
187 breaker.at(time).incrementAndCheckState();
188 }
189 assertTrue(breaker.isOpen(), "Not open");
190 breaker.close();
191 assertTrue(breaker.isClosed(), "Not closed");
192 assertTrue(breaker.at(time + 1000).incrementAndCheckState(), "Open again");
193 }
194
195
196
197
198
199 @Test
200 public void testClosingWhenThresholdReached() {
201 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD,
202 10, TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
203 breaker.open();
204 breaker.at(1000).incrementAndCheckState();
205 assertFalse(breaker.at(2000).checkState(), "Already closed");
206 assertFalse(breaker.at(NANO_FACTOR).checkState(), "Closed at interval end");
207 assertTrue(breaker.at(NANO_FACTOR + 1).checkState(), "Not closed after interval end");
208 assertTrue(breaker.isClosed(), "Not closed at end");
209 }
210
211
212
213
214
215 @Test
216 public void testDefaultClosingInterval() {
217 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
218 TimeUnit.SECONDS, CLOSING_THRESHOLD);
219 assertEquals(NANO_FACTOR, breaker.getClosingInterval(), "Wrong closing interval");
220 }
221
222
223
224
225
226 @Test
227 public void testDefaultClosingThreshold() {
228 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
229 TimeUnit.SECONDS);
230 assertEquals(NANO_FACTOR, breaker.getClosingInterval(), "Wrong closing interval");
231 assertEquals(OPENING_THRESHOLD, breaker.getClosingThreshold(), "Wrong closing threshold");
232 }
233
234
235
236
237 @Test
238 public void testInitiallyClosed() {
239 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
240 TimeUnit.SECONDS);
241 assertFalse(breaker.isOpen(), "Open");
242 assertTrue(breaker.isClosed(), "Not closed");
243 }
244
245
246
247
248 @Test
249 public void testIntervalCalculation() {
250 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
251 TimeUnit.SECONDS, CLOSING_THRESHOLD, 2, TimeUnit.MILLISECONDS);
252 assertEquals(NANO_FACTOR, breaker.getOpeningInterval(), "Wrong opening interval");
253 assertEquals(2 * NANO_FACTOR / 1000, breaker.getClosingInterval(), "Wrong closing interval");
254 }
255
256
257
258
259
260 @Test
261 public void testNotClosingOverThreshold() {
262 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD,
263 10, TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
264 long startTime = 0;
265 breaker.open();
266 for (int i = 0; i <= CLOSING_THRESHOLD; i++) {
267 assertFalse(breaker.at(startTime).incrementAndCheckState(), "Not open");
268 startTime += 1000;
269 }
270 assertFalse(breaker.at(startTime + NANO_FACTOR).incrementAndCheckState(), "Closed in new interval");
271 assertTrue(breaker.isOpen(), "Not open at end");
272 }
273
274
275
276
277
278 @Test
279 public void testNotOpeningCheckIntervalExceeded() {
280 long startTime = 0L;
281 final long timeIncrement = 3 * NANO_FACTOR / (2 * OPENING_THRESHOLD);
282 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
283 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
284 for (int i = 0; i < 5 * OPENING_THRESHOLD; i++) {
285 assertTrue(breaker.at(startTime).incrementAndCheckState(), "In open state");
286 startTime += timeIncrement;
287 }
288 assertTrue(breaker.isClosed(), "Not closed");
289 }
290
291
292
293
294
295 @Test
296 public void testNotOpeningUnderThreshold() {
297 long startTime = 1000;
298 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
299 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
300 for (int i = 0; i < OPENING_THRESHOLD - 1; i++) {
301 assertTrue(breaker.at(startTime).incrementAndCheckState(), "In open state");
302 startTime++;
303 }
304 assertTrue(breaker.isClosed(), "Not closed");
305 }
306
307
308
309
310 @Test
311 public void testNow() {
312 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
313 TimeUnit.SECONDS);
314 final long nowNanos = breaker.nanoTime();
315 final long deltaNanos = Math.abs(System.nanoTime() - nowNanos);
316 assertTrue(deltaNanos < 100_000, String.format("Delta %,d ns to current time too large", deltaNanos));
317 }
318
319
320
321
322 @Test
323 public void testOpeningWhenThresholdReached() {
324 long startTime = 0;
325 final long timeIncrement = NANO_FACTOR / OPENING_THRESHOLD - 1;
326 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
327 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
328 boolean open = false;
329 for (int i = 0; i < OPENING_THRESHOLD + 1; i++) {
330 open = !breaker.at(startTime).incrementAndCheckState();
331 startTime += timeIncrement;
332 }
333 assertTrue(open, "Not open");
334 assertFalse(breaker.isClosed(), "Closed");
335 }
336
337
338
339
340
341 @Test
342 public void testOpeningWhenThresholdReachedThroughBatch() {
343 final long timeIncrement = NANO_FACTOR / OPENING_THRESHOLD - 1;
344 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 1,
345 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
346 final long startTime = timeIncrement * (OPENING_THRESHOLD + 1);
347 final boolean open = !breaker.at(startTime).incrementAndCheckState(OPENING_THRESHOLD + 1);
348 assertTrue(open, "Not open");
349 assertFalse(breaker.isClosed(), "Closed");
350 }
351
352
353
354
355
356 @Test
357 public void testOpenStartsNewCheckInterval() {
358 final EventCountCircuitBreakerTestImpl breaker = new EventCountCircuitBreakerTestImpl(OPENING_THRESHOLD, 2,
359 TimeUnit.SECONDS, CLOSING_THRESHOLD, 1, TimeUnit.SECONDS);
360 breaker.at(NANO_FACTOR - 1000).open();
361 assertTrue(breaker.isOpen(), "Not open");
362 assertFalse(breaker.at(NANO_FACTOR + 100).checkState(), "Already closed");
363 }
364
365
366
367
368 @Test
369 public void testRemoveChangeListener() {
370 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
371 TimeUnit.SECONDS);
372 final ChangeListener listener = new ChangeListener(breaker);
373 breaker.addChangeListener(listener);
374 breaker.open();
375 breaker.removeChangeListener(listener);
376 breaker.close();
377 listener.verify(Boolean.TRUE);
378 }
379
380
381
382
383
384 @Test
385 public void testStateTransitionGuarded() throws InterruptedException {
386 final EventCountCircuitBreaker breaker = new EventCountCircuitBreaker(OPENING_THRESHOLD, 1,
387 TimeUnit.SECONDS);
388 final ChangeListener listener = new ChangeListener(breaker);
389 breaker.addChangeListener(listener);
390
391 final int threadCount = 128;
392 final CountDownLatch latch = new CountDownLatch(1);
393 final Thread[] threads = new Thread[threadCount];
394 for (int i = 0; i < threadCount; i++) {
395 threads[i] = new Thread() {
396 @Override
397 public void run() {
398 try {
399 latch.await();
400 } catch (final InterruptedException iex) {
401
402 }
403 breaker.open();
404 }
405 };
406 threads[i].start();
407 }
408 latch.countDown();
409 for (final Thread thread : threads) {
410 thread.join();
411 }
412 listener.verify(Boolean.TRUE);
413 }
414 }