View Javadoc
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 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   * Test class for {@code EventCountCircuitBreaker}.
39   */
40  class EventCountCircuitBreakerTest extends AbstractLangTest {
41  
42      /**
43       * A test change listener for checking whether correct change events are generated.
44       */
45      private static final class ChangeListener implements PropertyChangeListener {
46  
47          /** The expected event source. */
48          private final Object expectedSource;
49  
50          /** A list with the updated values extracted from received change events. */
51          private final List<Boolean> changedValues;
52  
53          /**
54           * Creates a new instance of {@code ChangeListener} and sets the expected event
55           * source.
56           *
57           * @param source The expected event source
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           * Verifies that change events for the expected values have been received.
76           *
77           * @param values The expected values
78           */
79          public void verify(final Boolean... values) {
80              assertArrayEquals(values, changedValues.toArray(ArrayUtils.EMPTY_BOOLEAN_OBJECT_ARRAY));
81          }
82      }
83  
84      /**
85       * A test implementation of {@code EventCountCircuitBreaker} which supports mocking the timer.
86       * This is useful for the creation of deterministic tests for switching the circuit
87       * breaker's state.
88       */
89      private static final class EventCountCircuitBreakerTestImpl extends EventCountCircuitBreaker {
90  
91          /** The current time in nanoseconds. */
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          * Sets the current time to be used by this test object for the next operation.
103          *
104          * @param time The time to set
105          * @return A reference to this object
106          */
107         public EventCountCircuitBreakerTestImpl at(final long time) {
108             currentTime = time;
109             return this;
110         }
111 
112         /**
113          * {@inheritDoc} This implementation returns the value passed to the {@code at()}
114          * method.
115          */
116         @Override
117         long nanoTime() {
118             return currentTime;
119         }
120     }
121 
122     /** Constant for the opening threshold. */
123     private static final int OPENING_THRESHOLD = 10;
124 
125     /** Constant for the closing threshold. */
126     private static final int CLOSING_THRESHOLD = 5;
127 
128     /** Constant for the factor for converting nanoseconds. */
129     private static final long NANO_FACTOR = 1000L * 1000L * 1000L;
130 
131     /**
132      * Tests whether a new check interval is started if the circuit breaker has a
133      * transition to open state.
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      * Tests whether events are generated when the state is changed.
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      * Tests that automatic state transitions generate change events as well.
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      * Tests whether the circuit breaker can be closed explicitly.
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      * Tests that the circuit breaker closes automatically if the number of events
201      * received goes under the closing threshold.
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      * Tests that the closing interval is the same as the opening interval if it is not
217      * specified.
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      * Tests that the closing threshold is the same as the opening threshold if not
228      * specified otherwise.
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      * Tests that a circuit breaker is closed after its creation.
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      * Tests that time units are correctly taken into account by constructors.
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      * Tests that a negative increment is rejected: the event count must only move toward
262      * the opening threshold.
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      * Tests that an open circuit breaker does not close itself when the number of events
272      * received is over the threshold.
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      * Tests that the circuit breaker stays closed if there are a number of received
290      * events, but not in a single check interval.
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      * Tests that the circuit breaker stays closed if the number of received events stays
307      * below the threshold.
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      * Tests whether the current time is correctly determined.
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      * Tests that the circuit breaker opens if all conditions are met.
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      * Tests that the circuit breaker opens if all conditions are met when using
353      * {@link EventCountCircuitBreaker#incrementAndCheckState(Integer increment)}.
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      * Tests whether an explicit open operation fully initializes the internal check data
368      * object. Otherwise, the circuit breaker may close itself directly afterwards.
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      * Tests whether a change listener can be removed.
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      * Tests that a state transition triggered by multiple threads is handled correctly.
396      * Only the first transition should cause an event to be sent.
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                         // ignore
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 }