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