View Javadoc

1   /*
2    * $Id: WaitPolicyTestCase.java 19239 2010-08-27 09:36:39Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.util.concurrent;
12  
13  import org.mule.tck.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  
23  import edu.emory.mathcs.backport.java.util.concurrent.LinkedBlockingQueue;
24  import edu.emory.mathcs.backport.java.util.concurrent.RejectedExecutionException;
25  import edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor;
26  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
27  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;
28  import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock;
29  
30  public class WaitPolicyTestCase extends AbstractMuleTestCase
31  {
32      private ExceptionCollectingThreadGroup threadGroup;
33      ThreadPoolExecutor executor;
34      ReentrantLock executorLock;
35  
36      @Override
37      protected void doSetUp() throws Exception
38      {
39          super.doSetUp();
40  
41          // allow 1 active & 1 queued Thread
42          executor = new ThreadPoolExecutor(1, 1, 10000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(1));
43          executor.prestartAllCoreThreads();
44  
45          // the lock must be fair to guarantee FIFO access to the executor;
46          // 'synchronized' on a monitor is not good enough.
47          executorLock = new ReentrantLock(true);
48  
49          // this is a Threadgroup that collects uncaught exceptions. Necessary for JDK
50          // 1.4.x only.
51          threadGroup = new ExceptionCollectingThreadGroup();
52  
53          // reset counter of active SleepyTasks
54          SleepyTask.activeTasks.set(0);
55      }
56  
57      @Override
58      protected void doTearDown() throws Exception
59      {
60          executor.shutdown();
61          threadGroup.destroy();
62          super.doTearDown();
63      }
64  
65      // Submit the given Runnables to an ExecutorService, but do so in separate
66      // threads to avoid blocking the test case when the Executors queue is full.
67      // Returns control to the caller when the threads have been started in
68      // order to avoid OS-dependent delays in the control flow.
69      // A submitting thread waits on a fair Lock to guarantee FIFO ordering.
70      // At the time of return the Runnables may or may not be in the queue,
71      // rejected, running or already finished. :-)
72      protected LinkedList<Thread> execute(final List<Runnable> tasks) throws InterruptedException
73      {
74          if (tasks == null || tasks.isEmpty())
75          {
76              throw new IllegalArgumentException("List<Runnable> must not be empty");
77          }
78  
79          LinkedList<Thread> submitters = new LinkedList<Thread>();
80  
81          executorLock.lock();
82  
83          for (Iterator<Runnable> i = tasks.iterator(); i.hasNext();)
84          {
85              final Runnable task = i.next();
86  
87              Runnable submitterAction = new Runnable()
88              {
89                  public void run()
90                  {
91                      // the lock is important because otherwise two submitters might
92                      // stumble over each other, submitting their runnables out-of-order
93                      // and causing test failures.
94                      try
95                      {
96                          executorLock.lock();
97                          executor.execute(task);
98                      }
99                      finally
100                     {
101                         executorLock.unlock();
102                     }
103                 }
104             };
105 
106             Thread submitter = new Thread(threadGroup, submitterAction);
107             submitter.setDaemon(true);
108             submitters.add(submitter);
109             submitter.start();
110 
111             // wait until the thread is actually enqueued on the lock
112             while (submitter.isAlive() && !executorLock.hasQueuedThread(submitter))
113             {
114                 Thread.sleep(10);
115             }
116         }
117 
118         executorLock.unlock();
119         return submitters;
120     }
121 
122     public void testWaitPolicyWithShutdownExecutor() throws Exception
123     {
124         assertEquals(0, SleepyTask.activeTasks.get());
125 
126         // wants to wait forever, but will fail immediately
127         executor.setRejectedExecutionHandler(new LastRejectedWaitPolicy());
128         executor.shutdown();
129 
130         // create a task
131         List<Runnable> tasks = new ArrayList<Runnable>();
132         tasks.add(new SleepyTask("rejected", 1000));
133 
134         // should fail and return immediately
135         LinkedList<Thread> submitters = this.execute(tasks);
136         assertFalse(submitters.isEmpty());
137 
138         // let submitted tasks run
139         Thread.sleep(1000);
140 
141         LinkedList<Map<Thread, Throwable>> exceptions = threadGroup.collectedExceptions();
142         assertEquals(1, exceptions.size());
143 
144         Map.Entry<Thread, Throwable> threadFailure = exceptions.getFirst().entrySet().iterator().next();
145         assertEquals(submitters.getFirst(), threadFailure.getKey());
146         assertEquals(RejectedExecutionException.class, threadFailure.getValue().getClass());
147         assertEquals(0, SleepyTask.activeTasks.get());
148     }
149 
150     public void testWaitPolicyForever() throws Exception
151     {
152         assertEquals(0, SleepyTask.activeTasks.get());
153 
154         // tasks wait forever
155         LastRejectedWaitPolicy policy = new LastRejectedWaitPolicy(-1, TimeUnit.SECONDS);
156         executor.setRejectedExecutionHandler(policy);
157 
158         // create tasks
159         List<Runnable> tasks = new ArrayList<Runnable>();
160         // task 1 runs immediately
161         tasks.add(new SleepyTask("run", 1000));
162         // task 2 is queued
163         tasks.add(new SleepyTask("queued", 1000));
164         // task 3 is initially rejected but waits forever
165         Runnable waiting = new SleepyTask("waitingForever", 1000);
166         tasks.add(waiting);
167 
168         // submit tasks
169         LinkedList<Thread> submitters = this.execute(tasks);
170         assertFalse(submitters.isEmpty());
171 
172         // the last task should have been queued
173         assertFalse(executor.awaitTermination(4000, TimeUnit.MILLISECONDS));
174         assertSame(waiting, policy.lastRejectedRunnable());
175         assertEquals(0, SleepyTask.activeTasks.get());
176     }
177 
178     public void testWaitPolicyWithTimeout() throws Exception
179     {
180         assertEquals(0, SleepyTask.activeTasks.get());
181 
182         // set a reasonable retry interval
183         LastRejectedWaitPolicy policy = new LastRejectedWaitPolicy(2500, TimeUnit.MILLISECONDS);
184         executor.setRejectedExecutionHandler(policy);
185 
186         // create tasks
187         List<Runnable> tasks = new ArrayList<Runnable>();
188         // task 1 runs immediately
189         tasks.add(new SleepyTask("run", 1000));
190         // task 2 is queued
191         tasks.add(new SleepyTask("queued", 1000));
192         // task 3 is initially rejected but will eventually succeed
193         Runnable waiting = new SleepyTask("waiting", 1000);
194         tasks.add(waiting);
195 
196         // submit tasks
197         LinkedList<Thread> submitters = this.execute(tasks);
198         assertFalse(submitters.isEmpty());
199 
200         assertFalse(executor.awaitTermination(5000, TimeUnit.MILLISECONDS));
201         assertSame(waiting, policy.lastRejectedRunnable());
202         assertEquals(0, SleepyTask.activeTasks.get());
203     }
204 
205     public void testWaitPolicyWithTimeoutFailure() throws Exception
206     {
207         assertEquals(0, SleepyTask.activeTasks.get());
208 
209         // set a really short wait interval
210         long failureInterval = 100L;
211         LastRejectedWaitPolicy policy = new LastRejectedWaitPolicy(failureInterval, TimeUnit.MILLISECONDS);
212         executor.setRejectedExecutionHandler(policy);
213 
214         // create tasks
215         List<Runnable> tasks = new ArrayList<Runnable>();
216         // task 1 runs immediately
217         tasks.add(new SleepyTask("run", 1000));
218         // task 2 is queued
219         tasks.add(new SleepyTask("queued", 1000));
220         // task 3 is initially rejected & will retry but should fail quickly
221         Runnable failedTask = new SleepyTask("waitAndFail", 1000);
222         tasks.add(failedTask);
223 
224         // submit tasks
225         LinkedList<Thread> submitters = this.execute(tasks);
226         assertFalse(submitters.isEmpty());
227 
228         // give failure a chance
229         Thread.sleep(failureInterval * 10);
230 
231         // make sure there was one failure
232         LinkedList<Map<Thread, Throwable>>  exceptions = threadGroup.collectedExceptions();
233         assertEquals(1, exceptions.size());
234 
235         // make sure the failed task was the right one
236         Map.Entry<Thread, Throwable> threadFailure = exceptions.getFirst().entrySet().iterator().next();
237         assertEquals(submitters.getLast(), threadFailure.getKey());
238         assertEquals(RejectedExecutionException.class, threadFailure.getValue().getClass());
239 
240         executor.shutdown();
241         assertTrue(executor.awaitTermination(2500, TimeUnit.MILLISECONDS));
242         assertSame(failedTask, policy.lastRejectedRunnable());
243         assertEquals(0, SleepyTask.activeTasks.get());
244     }
245 }
246 
247 class LastRejectedWaitPolicy extends WaitPolicy
248 {
249     // needed to hand the last rejected Runnable back to the TestCase
250     private volatile Runnable _rejected;
251 
252     public LastRejectedWaitPolicy()
253     {
254         super();
255     }
256 
257     public LastRejectedWaitPolicy(long time, TimeUnit timeUnit)
258     {
259         super(time, timeUnit);
260     }
261 
262     public Runnable lastRejectedRunnable()
263     {
264         return _rejected;
265     }
266 
267     @Override
268     public void rejectedExecution(Runnable r, ThreadPoolExecutor e)
269     {
270         _rejected = r;
271         super.rejectedExecution(r, e);
272     }
273 }
274 
275 // task to execute - just sleeps for the given interval
276 class SleepyTask extends Object implements Runnable
277 {
278     public static final AtomicInteger activeTasks = new AtomicInteger(0);
279 
280     private final String name;
281     private final long sleepTime;
282 
283     public SleepyTask(String name, long sleepTime)
284     {
285         if (StringUtils.isEmpty(name))
286         {
287             throw new IllegalArgumentException("SleepyTask needs a name!");
288         }
289 
290         this.name = name;
291         this.sleepTime = sleepTime;
292     }
293 
294     @Override
295     public String toString()
296     {
297         return this.getClass().getName() + '{' + name + ", " + sleepTime + '}';
298     }
299 
300     public void run()
301     {
302         activeTasks.incrementAndGet();
303 
304         try
305         {
306             Thread.sleep(sleepTime);
307         }
308         catch (InterruptedException iex)
309         {
310             Thread.currentThread().interrupt();
311         }
312         finally
313         {
314             activeTasks.decrementAndGet();
315         }
316     }
317 
318 }
319 
320 // ThreadGroup wrapper that collects uncaught exceptions
321 class ExceptionCollectingThreadGroup extends ThreadGroup
322 {
323     private final LinkedList<Map<Thread, Throwable>> exceptions = new LinkedList<Map<Thread, Throwable>>();
324 
325     public ExceptionCollectingThreadGroup()
326     {
327         super("ExceptionCollectingThreadGroup");
328     }
329 
330     // collected Map(Thread, Throwable) associations
331     public LinkedList<Map<Thread, Throwable>> collectedExceptions()
332     {
333         return exceptions;
334     }
335 
336     // all uncaught Thread exceptions end up here
337     @Override
338     public void uncaughtException(Thread t, Throwable e)
339     {
340         synchronized (exceptions)
341         {
342             exceptions.add(Collections.singletonMap(t, e));
343         }
344     }
345 }