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.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.time.Duration;
28  import java.util.concurrent.CountDownLatch;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.TimeUnit;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  import java.util.concurrent.atomic.AtomicInteger;
34  import java.util.concurrent.atomic.AtomicReference;
35  
36  import org.apache.commons.lang3.AbstractLangTest;
37  import org.apache.commons.lang3.ThreadUtils;
38  import org.junit.jupiter.api.Test;
39  
40  public class BackgroundInitializerTest extends AbstractLangTest {
41      /**
42       * A concrete implementation of BackgroundInitializer. It also overloads
43       * some methods that simplify testing.
44       */
45      protected static class AbstractBackgroundInitializerTestImpl extends
46              BackgroundInitializer<CloseableCounter> {
47          /** An exception to be thrown by initialize(). */
48          Exception ex;
49  
50          /** A flag whether the background task should sleep a while. */
51          boolean shouldSleep;
52  
53          /** A latch tests can use to control when initialize completes. */
54          final CountDownLatch latch = new CountDownLatch(1);
55          boolean waitForLatch;
56  
57          /** An object containing the state we are testing */
58          CloseableCounter counter = new CloseableCounter();
59  
60          AbstractBackgroundInitializerTestImpl() {
61          }
62  
63          AbstractBackgroundInitializerTestImpl(final ExecutorService exec) {
64              super(exec);
65          }
66  
67          public void enableLatch() {
68              waitForLatch = true;
69          }
70  
71          public CloseableCounter getCloseableCounter() {
72              return counter;
73          }
74  
75          /**
76           * Records this invocation. Optionally throws an exception or sleeps a
77           * while.
78           *
79           * @throws Exception in case of an error
80           */
81          protected CloseableCounter initializeInternal() throws Exception {
82              if (ex != null) {
83                  throw ex;
84              }
85              if (shouldSleep) {
86                  ThreadUtils.sleep(Duration.ofMinutes(1));
87              }
88              if (waitForLatch) {
89                  latch.await();
90              }
91              return counter.increment();
92          }
93  
94          public void releaseLatch() {
95              latch.countDown();
96          }
97      }
98  
99      protected static class CloseableCounter {
100         /** The number of invocations of initialize(). */
101         AtomicInteger initializeCalls = new AtomicInteger();
102 
103         /** Has the close consumer successfully reached this object. */
104         AtomicBoolean closed = new AtomicBoolean();
105 
106         public void close() {
107             closed.set(true);
108         }
109 
110         public int getInitializeCalls() {
111             return initializeCalls.get();
112         }
113 
114         public CloseableCounter increment() {
115             initializeCalls.incrementAndGet();
116             return this;
117         }
118 
119         public boolean isClosed() {
120             return closed.get();
121         }
122     }
123 
124     protected static class MethodBackgroundInitializerTestImpl extends AbstractBackgroundInitializerTestImpl {
125 
126         MethodBackgroundInitializerTestImpl() {
127         }
128 
129         MethodBackgroundInitializerTestImpl(final ExecutorService exec) {
130             super(exec);
131         }
132 
133         @Override
134         protected CloseableCounter initialize() throws Exception {
135             return initializeInternal();
136         }
137     }
138 
139     /**
140      * Helper method for checking whether the initialize() method was correctly
141      * called. start() must already have been invoked.
142      *
143      * @param init the initializer to test
144      */
145     private void checkInitialize(final AbstractBackgroundInitializerTestImpl init) throws ConcurrentException {
146         final Integer result = init.get().getInitializeCalls();
147         assertEquals(1, result.intValue(), "Wrong result");
148         assertEquals(1, init.getCloseableCounter().getInitializeCalls(), "Wrong number of invocations");
149         assertNotNull(init.getFuture(), "No future");
150     }
151 
152     protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl() {
153         return new MethodBackgroundInitializerTestImpl();
154     }
155 
156     protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl(final ExecutorService exec) {
157         return new MethodBackgroundInitializerTestImpl(exec);
158     }
159 
160     /**
161      * Tries to obtain the executor before start(). It should not have been
162      * initialized yet.
163      */
164     @Test
165     public void testGetActiveExecutorBeforeStart() {
166         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
167         assertNull(init.getActiveExecutor(), "Got an executor");
168     }
169 
170     /**
171      * Tests whether an external executor is correctly detected.
172      */
173     @Test
174     public void testGetActiveExecutorExternal() throws InterruptedException, ConcurrentException {
175         final ExecutorService exec = Executors.newSingleThreadExecutor();
176         try {
177             final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
178                     exec);
179             init.start();
180             assertSame(exec, init.getActiveExecutor(), "Wrong executor");
181             checkInitialize(init);
182         } finally {
183             exec.shutdown();
184             exec.awaitTermination(1, TimeUnit.SECONDS);
185         }
186     }
187 
188     /**
189      * Tests getActiveExecutor() for a temporary executor.
190      */
191     @Test
192     public void testGetActiveExecutorTemp() throws ConcurrentException {
193         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
194         init.start();
195         assertNotNull(init.getActiveExecutor(), "No active executor");
196         checkInitialize(init);
197     }
198 
199     /**
200      * Tests calling get() before start(). This should cause an exception.
201      */
202     @Test
203     public void testGetBeforeStart() {
204         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
205         assertThrows(IllegalStateException.class, init::get);
206     }
207 
208     /**
209      * Tests the get() method if background processing causes a checked
210      * exception.
211      */
212     @Test
213     public void testGetCheckedException() {
214         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
215         final Exception ex = new Exception();
216         init.ex = ex;
217         init.start();
218         final ConcurrentException cex = assertThrows(ConcurrentException.class, init::get);
219         assertEquals(ex, cex.getCause(), "Exception not thrown");
220     }
221 
222     /**
223      * Tests the get() method if waiting for the initialization is interrupted.
224      *
225      * @throws InterruptedException because we're making use of Java's concurrent API
226      */
227     @Test
228     public void testGetInterruptedException() throws InterruptedException {
229         final ExecutorService exec = Executors.newSingleThreadExecutor();
230         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
231                 exec);
232         final CountDownLatch latch1 = new CountDownLatch(1);
233         init.shouldSleep = true;
234         init.start();
235         final AtomicReference<InterruptedException> iex = new AtomicReference<>();
236         final Thread getThread = new Thread() {
237             @Override
238             public void run() {
239                 try {
240                     init.get();
241                 } catch (final ConcurrentException cex) {
242                     if (cex.getCause() instanceof InterruptedException) {
243                         iex.set((InterruptedException) cex.getCause());
244                     }
245                 } finally {
246                     assertTrue(isInterrupted(), "Thread not interrupted");
247                     latch1.countDown();
248                 }
249             }
250         };
251         getThread.start();
252         getThread.interrupt();
253         latch1.await();
254         exec.shutdownNow();
255         exec.awaitTermination(1, TimeUnit.SECONDS);
256         assertNotNull(iex.get(), "No interrupted exception");
257     }
258 
259     /**
260      * Tests the get() method if background processing causes a runtime
261      * exception.
262      */
263     @Test
264     public void testGetRuntimeException() {
265         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
266         final RuntimeException rex = new RuntimeException();
267         init.ex = rex;
268         init.start();
269         final Exception ex = assertThrows(Exception.class, init::get);
270         assertEquals(rex, ex, "Runtime exception not thrown");
271     }
272 
273     /**
274      * Tests whether initialize() is invoked.
275      */
276     @Test
277     public void testInitialize() throws ConcurrentException {
278         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
279         init.start();
280         checkInitialize(init);
281     }
282 
283     /**
284      * Tests the execution of the background task if a temporary executor has to
285      * be created.
286      */
287     @Test
288     public void testInitializeTempExecutor() throws ConcurrentException {
289         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
290         assertTrue(init.start(), "Wrong result of start()");
291         checkInitialize(init);
292         assertTrue(init.getActiveExecutor().isShutdown(), "Executor not shutdown");
293     }
294 
295     /**
296      * Tests isInitialized() before and after the background task has finished.
297      */
298     @Test
299     public void testIsInitialized() throws ConcurrentException {
300         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
301         init.enableLatch();
302         init.start();
303         assertTrue(init.isStarted(), "Not started"); //Started and Initialized should return opposite values
304         assertFalse(init.isInitialized(), "Initalized before releasing latch");
305         init.releaseLatch();
306         init.get(); //to ensure the initialize thread has completed.
307         assertTrue(init.isInitialized(), "Not initalized after releasing latch");
308     }
309 
310     /**
311      * Tests isStarted() after the background task has finished.
312      */
313     @Test
314     public void testIsStartedAfterGet() throws ConcurrentException {
315         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
316         init.start();
317         checkInitialize(init);
318         assertTrue(init.isStarted(), "Not started");
319     }
320 
321     /**
322      * Tests isStarted() before start() was called.
323      */
324     @Test
325     public void testIsStartedFalse() {
326         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
327         assertFalse(init.isStarted(), "Already started");
328     }
329 
330     /**
331      * Tests isStarted() after start().
332      */
333     @Test
334     public void testIsStartedTrue() {
335         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
336         init.start();
337         assertTrue(init.isStarted(), "Not started");
338     }
339 
340     /**
341      * Tests whether an external executor can be set using the
342      * setExternalExecutor() method.
343      */
344     @Test
345     public void testSetExternalExecutor() throws ConcurrentException {
346         final ExecutorService exec = Executors.newCachedThreadPool();
347         try {
348             final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
349             init.setExternalExecutor(exec);
350             assertEquals(exec, init.getExternalExecutor(), "Wrong executor service");
351             assertTrue(init.start(), "Wrong result of start()");
352             assertSame(exec, init.getActiveExecutor(), "Wrong active executor");
353             checkInitialize(init);
354             assertFalse(exec.isShutdown(), "Executor was shutdown");
355         } finally {
356             exec.shutdown();
357         }
358     }
359 
360     /**
361      * Tests that setting an executor after start() causes an exception.
362      *
363      * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the test implementation may throw it
364      */
365     @Test
366     public void testSetExternalExecutorAfterStart() throws ConcurrentException, InterruptedException {
367         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
368         init.start();
369         final ExecutorService exec = Executors.newSingleThreadExecutor();
370         try {
371             assertThrows(IllegalStateException.class, () -> init.setExternalExecutor(exec));
372             init.get();
373         } finally {
374             exec.shutdown();
375             exec.awaitTermination(1, TimeUnit.SECONDS);
376         }
377     }
378 
379     /**
380      * Tests invoking start() multiple times. Only the first invocation should
381      * have an effect.
382      */
383     @Test
384     public void testStartMultipleTimes() throws ConcurrentException {
385         final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
386         assertTrue(init.start(), "Wrong result for start()");
387         for (int i = 0; i < 10; i++) {
388             assertFalse(init.start(), "Could start again");
389         }
390         checkInitialize(init);
391     }
392 }