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
30
31
32
33
34
35
36
37
38
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
48 throw new RuntimeException(e);
49
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
65
66
67
68
69 createCallCount.incrementAndGet();
70 pause();
71 pause();
72 pause();
73 pause();
74 pause();
75 return new Object();
76 }
77 };
78
79
80
81
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
93
94
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
106 assertEquals(1, createCallCount.get());
107
108
109
110
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
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!";
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
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
277 @Override protected String create() throws Exception {
278 return "created!";
279 }
280
281 };
282 ref.cancel();
283 ref.get();
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 }