1
2
3
4
5
6
7
8
9
10
11 package org.mule.util.concurrent;
12
13 import org.mule.tck.junit4.AbstractMuleTestCase;
14 import org.mule.util.StringUtils;
15
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.Iterator;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.concurrent.LinkedBlockingQueue;
23 import java.util.concurrent.RejectedExecutionException;
24 import java.util.concurrent.ThreadPoolExecutor;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.atomic.AtomicInteger;
27 import java.util.concurrent.locks.ReentrantLock;
28
29 import org.junit.After;
30 import org.junit.Before;
31 import org.junit.Test;
32
33 import static junit.framework.Assert.assertFalse;
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertSame;
36 import static org.junit.Assert.assertTrue;
37
38 public class WaitPolicyTestCase extends AbstractMuleTestCase
39 {
40 private ExceptionCollectingThreadGroup threadGroup;
41 ThreadPoolExecutor executor;
42 ReentrantLock executorLock;
43
44 @Before
45 public void startExecutor()
46 {
47
48 executor = new ThreadPoolExecutor(1, 1, 10000L, TimeUnit.MILLISECONDS,
49 new LinkedBlockingQueue<Runnable>(1));
50 executor.prestartAllCoreThreads();
51
52
53
54 executorLock = new ReentrantLock(true);
55
56
57
58 threadGroup = new ExceptionCollectingThreadGroup();
59
60
61 SleepyTask.activeTasks.set(0);
62 }
63
64 @After
65 public void shutDownExecutor()
66 {
67 executor.shutdown();
68 threadGroup.destroy();
69 }
70
71
72
73
74
75
76
77
78 protected LinkedList<Thread> execute(final List<Runnable> tasks) throws InterruptedException
79 {
80 if (tasks == null || tasks.isEmpty())
81 {
82 throw new IllegalArgumentException("List<Runnable> must not be empty");
83 }
84
85 LinkedList<Thread> submitters = new LinkedList<Thread>();
86
87 executorLock.lock();
88
89 for (Iterator<Runnable> i = tasks.iterator(); i.hasNext();)
90 {
91 final Runnable task = i.next();
92
93 Runnable submitterAction = new Runnable()
94 {
95 @Override
96 public void run()
97 {
98
99
100
101 try
102 {
103 executorLock.lock();
104 executor.execute(task);
105 }
106 finally
107 {
108 executorLock.unlock();
109 }
110 }
111 };
112
113 Thread submitter = new Thread(threadGroup, submitterAction);
114 submitter.setDaemon(true);
115 submitters.add(submitter);
116 submitter.start();
117
118
119 while (submitter.isAlive() && !executorLock.hasQueuedThread(submitter))
120 {
121 Thread.sleep(10);
122 }
123 }
124
125 executorLock.unlock();
126 return submitters;
127 }
128
129 @Test
130 public void testWaitPolicyWithShutdownExecutor() throws Exception
131 {
132 assertEquals(0, SleepyTask.activeTasks.get());
133
134
135 executor.setRejectedExecutionHandler(new LastRejectedWaitPolicy());
136 executor.shutdown();
137
138
139 List<Runnable> tasks = new ArrayList<Runnable>();
140 tasks.add(new SleepyTask("rejected", 1000));
141
142
143 LinkedList<Thread> submitters = this.execute(tasks);
144 assertFalse(submitters.isEmpty());
145
146
147 Thread.sleep(1000);
148
149 LinkedList<Map<Thread, Throwable>> exceptions = threadGroup.collectedExceptions();
150 assertEquals(1, exceptions.size());
151
152 Map.Entry<Thread, Throwable> threadFailure = exceptions.getFirst().entrySet().iterator().next();
153 assertEquals(submitters.getFirst(), threadFailure.getKey());
154 assertEquals(RejectedExecutionException.class, threadFailure.getValue().getClass());
155 assertEquals(0, SleepyTask.activeTasks.get());
156 }
157
158 @Test
159 public void testWaitPolicyForever() throws Exception
160 {
161 assertEquals(0, SleepyTask.activeTasks.get());
162
163
164 LastRejectedWaitPolicy policy = new LastRejectedWaitPolicy(-1, TimeUnit.SECONDS);
165 executor.setRejectedExecutionHandler(policy);
166
167
168 List<Runnable> tasks = new ArrayList<Runnable>();
169
170 tasks.add(new SleepyTask("run", 1000));
171
172 tasks.add(new SleepyTask("queued", 1000));
173
174 Runnable waiting = new SleepyTask("waitingForever", 1000);
175 tasks.add(waiting);
176
177
178 LinkedList<Thread> submitters = this.execute(tasks);
179 assertFalse(submitters.isEmpty());
180
181
182 assertFalse(executor.awaitTermination(4000, TimeUnit.MILLISECONDS));
183 assertSame(waiting, policy.lastRejectedRunnable());
184 assertEquals(0, SleepyTask.activeTasks.get());
185 }
186
187 @Test
188 public void testWaitPolicyWithTimeout() throws Exception
189 {
190 assertEquals(0, SleepyTask.activeTasks.get());
191
192
193 LastRejectedWaitPolicy policy = new LastRejectedWaitPolicy(2500, TimeUnit.MILLISECONDS);
194 executor.setRejectedExecutionHandler(policy);
195
196
197 List<Runnable> tasks = new ArrayList<Runnable>();
198
199 tasks.add(new SleepyTask("run", 1000));
200
201 tasks.add(new SleepyTask("queued", 1000));
202
203 Runnable waiting = new SleepyTask("waiting", 1000);
204 tasks.add(waiting);
205
206
207 LinkedList<Thread> submitters = this.execute(tasks);
208 assertFalse(submitters.isEmpty());
209
210 assertFalse(executor.awaitTermination(5000, TimeUnit.MILLISECONDS));
211 assertSame(waiting, policy.lastRejectedRunnable());
212 assertEquals(0, SleepyTask.activeTasks.get());
213 }
214
215 @Test
216 public void testWaitPolicyWithTimeoutFailure() throws Exception
217 {
218 assertEquals(0, SleepyTask.activeTasks.get());
219
220
221 long failureInterval = 100L;
222 LastRejectedWaitPolicy policy = new LastRejectedWaitPolicy(failureInterval, TimeUnit.MILLISECONDS);
223 executor.setRejectedExecutionHandler(policy);
224
225
226 List<Runnable> tasks = new ArrayList<Runnable>();
227
228 tasks.add(new SleepyTask("run", 1000));
229
230 tasks.add(new SleepyTask("queued", 1000));
231
232 Runnable failedTask = new SleepyTask("waitAndFail", 1000);
233 tasks.add(failedTask);
234
235
236 LinkedList<Thread> submitters = this.execute(tasks);
237 assertFalse(submitters.isEmpty());
238
239
240 Thread.sleep(failureInterval * 10);
241
242
243 LinkedList<Map<Thread, Throwable>> exceptions = threadGroup.collectedExceptions();
244 assertEquals(1, exceptions.size());
245
246
247 Map.Entry<Thread, Throwable> threadFailure = exceptions.getFirst().entrySet().iterator().next();
248 assertEquals(submitters.getLast(), threadFailure.getKey());
249 assertEquals(RejectedExecutionException.class, threadFailure.getValue().getClass());
250
251 executor.shutdown();
252 assertTrue(executor.awaitTermination(2500, TimeUnit.MILLISECONDS));
253 assertSame(failedTask, policy.lastRejectedRunnable());
254 assertEquals(0, SleepyTask.activeTasks.get());
255 }
256 }
257
258 class LastRejectedWaitPolicy extends WaitPolicy
259 {
260
261 private volatile Runnable _rejected;
262
263 public LastRejectedWaitPolicy()
264 {
265 super();
266 }
267
268 public LastRejectedWaitPolicy(long time, TimeUnit timeUnit)
269 {
270 super(time, timeUnit);
271 }
272
273 public Runnable lastRejectedRunnable()
274 {
275 return _rejected;
276 }
277
278 @Override
279 public void rejectedExecution(Runnable r, ThreadPoolExecutor e)
280 {
281 _rejected = r;
282 super.rejectedExecution(r, e);
283 }
284 }
285
286
287 class SleepyTask extends Object implements Runnable
288 {
289 public static final AtomicInteger activeTasks = new AtomicInteger(0);
290
291 private final String name;
292 private final long sleepTime;
293
294 public SleepyTask(String name, long sleepTime)
295 {
296 if (StringUtils.isEmpty(name))
297 {
298 throw new IllegalArgumentException("SleepyTask needs a name!");
299 }
300
301 this.name = name;
302 this.sleepTime = sleepTime;
303 }
304
305 @Override
306 public String toString()
307 {
308 return this.getClass().getName() + '{' + name + ", " + sleepTime + '}';
309 }
310
311 @Override
312 public void run()
313 {
314 activeTasks.incrementAndGet();
315
316 try
317 {
318 Thread.sleep(sleepTime);
319 }
320 catch (InterruptedException iex)
321 {
322 Thread.currentThread().interrupt();
323 }
324 finally
325 {
326 activeTasks.decrementAndGet();
327 }
328 }
329
330 }
331
332
333 class ExceptionCollectingThreadGroup extends ThreadGroup
334 {
335 private final LinkedList<Map<Thread, Throwable>> exceptions = new LinkedList<Map<Thread, Throwable>>();
336
337 public ExceptionCollectingThreadGroup()
338 {
339 super("ExceptionCollectingThreadGroup");
340 }
341
342
343 public LinkedList<Map<Thread, Throwable>> collectedExceptions()
344 {
345 return exceptions;
346 }
347
348
349 @Override
350 public void uncaughtException(Thread t, Throwable e)
351 {
352 synchronized (exceptions)
353 {
354 exceptions.add(Collections.singletonMap(t, e));
355 }
356 }
357 }