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