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