View Javadoc

1   package io.atlassian.fugue;
2   
3   import io.atlassian.fugue.auc.BooleanLatch;
4   import org.junit.Test;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   import java.util.concurrent.Callable;
9   import java.util.concurrent.CancellationException;
10  import java.util.concurrent.CountDownLatch;
11  import java.util.concurrent.ExecutionException;
12  import java.util.concurrent.ExecutorService;
13  import java.util.concurrent.Executors;
14  import java.util.concurrent.Future;
15  import java.util.concurrent.atomic.AtomicInteger;
16  import java.util.concurrent.atomic.AtomicReference;
17  
18  import static org.junit.Assert.assertEquals;
19  import static org.junit.Assert.assertFalse;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertSame;
23  import static org.junit.Assert.assertTrue;
24  import static org.junit.Assert.fail;
25  
26  public class LazyReferenceTest {
27  
28    // /**
29    // * Used to pound the tests
30    // *
31    // * @throws Exception
32    // */
33    // public static void main(final String[] args) throws Exception {
34    // final Iterables.LazyReferenceTest test = new Iterables.LazyReferenceTest();
35    // for (int i = 0; i < 10000; i++) {
36    // // test.concurrentCreate();
37    // // test.getInterruptibly();
38    // test.getNotInterruptable();
39    // }
40    // }
41    static final int WAIT = 10;
42  
43    static void pause(final int millis) {
44      try {
45        Thread.sleep(millis);
46      } catch (final InterruptedException e) {
47        // /CLOVER:OFF
48        throw new RuntimeException(e);
49        // /CLOVER:ON
50      }
51    }
52  
53    static void pause() {
54      pause(WAIT);
55    }
56  
57    @Test public void concurrentCreate() throws Exception {
58      final int nThreads = 40;
59      final Object[] results = new Object[nThreads];
60      final AtomicInteger createCallCount = new AtomicInteger(0);
61      final Iterables.LazyReference<Object> ref = new Iterables.LazyReference<Object>() {
62        @Override protected Object create() {
63          /*
64           * We are trying to simulate an expensive object construction call. So
65           * we do a sleep here. The idea is that we will get many threads to call
66           * create() at the same time, make create "slow" and then ensure that
67           * create() method was indeed invoked only once.
68           */
69          createCallCount.incrementAndGet();
70          pause();
71          pause();
72          pause();
73          pause();
74          pause();
75          return new Object();
76        }
77      };
78  
79      /*
80       * pool size must be large enough to accommodate all Callables running in
81       * parallel as they latch
82       */
83      final ExecutorService pool = Executors.newFixedThreadPool(nThreads);
84      final CountDownLatch latch = new CountDownLatch(nThreads);
85  
86      final List<Callable<Object>> tasks = new ArrayList<>(nThreads);
87  
88      for (int i = 0; i < nThreads; i++) {
89        final int j = i;
90        tasks.add(() -> {
91          /*
92           * Put in a latch to synchronize all threads and try to get them to call
93           * ref.get() at the same time (to increase concurrency and make this
94           * test more useful)
95           */
96          latch.countDown();
97          latch.await();
98          results[j] = ref.get();
99          return results[j];
100       });
101     }
102 
103     final List<Future<Object>> futures = pool.invokeAll(tasks);
104 
105     // Ensure the create() method was invoked once
106     assertEquals(1, createCallCount.get());
107 
108     /*
109      * Ensure that all the references are the same, use the futures in case of
110      * exception
111      */
112     final Object result = results[0];
113     for (final Future<Object> future : futures) {
114       assertSame(result, future.get());
115     }
116     for (int i = 0; i < results.length; i++) {
117       assertSame("got back different reference in '" + i + "' place", result, results[i]);
118     }
119     pool.shutdown();
120   }
121 
122   @Test public void exception() {
123     final Exception myException = new Exception();
124 
125     final Iterables.LazyReference<Object> ref = new Iterables.LazyReference<Object>() {
126       @Override protected Object create() throws Exception {
127         throw myException;
128       }
129     };
130 
131     try {
132       ref.get();
133       fail("RuntimeException should have been thrown");
134     } catch (final RuntimeException yay) {
135       assertNotNull(yay.getCause());
136       assertTrue(myException == yay.getCause());
137     }
138   }
139 
140   @Test public void getNotInterruptibleDoesntThrow() {
141     final BooleanLatch latch = new BooleanLatch();
142 
143     final Iterables.LazyReference<Integer> ref = new Iterables.LazyReference<Integer>() {
144       @Override protected Integer create() {
145         // do not interrupt
146         while (true) {
147           try {
148             latch.await();
149             return 10;
150           } catch (final InterruptedException e) {}
151         }
152       }
153     };
154 
155     final Thread client = new Thread(ref::get, this.getClass().getName());
156     client.start();
157 
158     for (int i = 0; i < 10; i++) {
159       pause();
160       assertFalse(ref.isInitialized());
161       client.interrupt();
162     }
163     pause();
164     assertFalse(ref.isInitialized());
165 
166     latch.release();
167     pause();
168     assertTrue(ref.isInitialized());
169 
170     final int obj = ref.get();
171     assertEquals(10, obj);
172   }
173 
174   @Test public void getResetsInterrupted() throws Exception {
175     final Iterables.LazyReference<String> ref = new Iterables.LazyReference<String>() {
176       @Override protected String create() throws Exception {
177         return "test!";// exchange.get();
178       }
179     };
180     Thread.currentThread().interrupt();
181     ref.get();
182     assertTrue(Thread.interrupted());
183   }
184 
185   @Test(expected = InterruptedException.class) public void getInterruptiblyThrowsInterrupted() throws Exception {
186     final Iterables.LazyReference<String> ref = new Iterables.LazyReference<String>() {
187       @Override protected String create() throws Exception {
188         return "test";
189       }
190     };
191     Thread.currentThread().interrupt();
192     ref.getInterruptibly();
193   }
194 
195   @Test public void getInterruptibly() throws Exception {
196     final class Result<T> {
197       final T result;
198       final Exception exception;
199 
200       Result(final T result) {
201         this.result = result;
202         this.exception = null;
203       }
204 
205       Result(final Exception exception) {
206         this.result = null;
207         this.exception = exception;
208       }
209     }
210     final BooleanLatch latch = new BooleanLatch();
211 
212     final Iterables.LazyReference<Integer> ref = new Iterables.LazyReference<Integer>() {
213       @Override protected Integer create() {
214         // do not interrupt
215         while (true) {
216           try {
217             latch.await();
218             return 10;
219           } catch (final InterruptedException e) {}
220         }
221       }
222     };
223 
224     final AtomicReference<Result<Integer>> result1 = new AtomicReference<>();
225     final Thread client1 = new Thread(() -> {
226       try {
227         result1.compareAndSet(null, new Result<>(ref.getInterruptibly()));
228       } catch (final Exception e) {
229         result1.compareAndSet(null, new Result<>(e));
230       }
231     }, this.getClass().getName());
232     client1.start();
233 
234     pause();
235     final AtomicReference<Result<Integer>> result2 = new AtomicReference<>();
236     final Thread client2 = new Thread(() -> {
237       try {
238         result2.compareAndSet(null, new Result<>(ref.getInterruptibly()));
239       } catch (final Exception e) {
240         result2.compareAndSet(null, new Result<>(e));
241       }
242     }, this.getClass().getName());
243     client2.start();
244 
245     for (int i = 0; i < 10; i++) {
246       pause();
247       assertFalse(ref.isInitialized());
248       client1.interrupt();
249       client2.interrupt();
250     }
251 
252     assertNull(result1.get());
253     assertNotNull(result2.get().exception);
254     assertEquals(InterruptedException.class, result2.get().exception.getClass());
255     pause();
256     assertFalse(ref.isInitialized());
257 
258     latch.release();
259     pause();
260     assertTrue(ref.isInitialized());
261 
262     {
263       final int result = ref.get();
264       assertEquals(10, result);
265     }
266     assertNotNull(result1.get());
267     assertNotNull(result1.get().result);
268     {
269       final int result = result1.get().result;
270       assertEquals(10, result);
271     }
272   }
273 
274   @Test(expected = CancellationException.class) public void cancellable() throws Exception {
275     final Iterables.LazyReference<String> ref = new Iterables.LazyReference<String>() {
276       // /CLOVER:OFF
277       @Override protected String create() throws Exception {
278         return "created!";
279       }
280       // /CLOVER:ON
281     };
282     ref.cancel();
283     ref.get(); // throws
284   }
285 
286   @Test public void initExConstructorWithBlankExecExCause() throws Exception {
287     @SuppressWarnings("serial")
288     final ExecutionException e = new ExecutionException("") {};
289     assertSame(e, new Iterables.LazyReference.InitializationException(e).getCause());
290   }
291 
292   @Test public void initExConstructorWithRealExecExCause() throws Exception {
293     final NoSuchMethodError er = new NoSuchMethodError();
294     assertSame(er, new Iterables.LazyReference.InitializationException(new ExecutionException("", er)).getCause());
295   }
296 
297   @Test(expected = Iterables.LazyReference.InitializationException.class) public void reentrancy() {
298     new Iterables.LazyReference<String>() {
299       @Override protected String create() throws Exception {
300         return this.get();
301       }
302     }.get();
303   }
304 }