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