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