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