1   /*
2    * $Id: WaitPolicyTestCase.java 7976 2007-08-21 14:26:13Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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      private ThreadPoolExecutor executor;
34      private 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 submitters = new LinkedList();
80  
81          executorLock.lock();
82  
83          for (Iterator i = tasks.iterator(); i.hasNext();)
84          {
85              final Runnable task = (Runnable)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 tasks = new ArrayList();
132         tasks.add(new SleepyTask("rejected", 1000));
133 
134         // should fail and return immediately
135         LinkedList submitters = this.execute(tasks);
136         assertFalse(submitters.isEmpty());
137 
138         // let submitted tasks run
139         Thread.sleep(1000);
140 
141         LinkedList exceptions = threadGroup.collectedExceptions();
142         assertEquals(1, exceptions.size());
143 
144         Map.Entry threadFailure = (Map.Entry)((Map)(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 tasks = new ArrayList();
160         // task 1 runs immediately
161         tasks.add(new SleepyTask("hans", 1000));
162         // task 2 is queued
163         tasks.add(new SleepyTask("franz", 1000));
164         // task 3 is initially rejected but waits forever
165         Runnable t3 = new SleepyTask("beavis", 1000);
166         tasks.add(t3);
167 
168         // submit tasks
169         LinkedList 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(t3, 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 tasks = new ArrayList();
188         // task 1 runs immediately
189         tasks.add(new SleepyTask("hans", 1000));
190         // task 2 is queued
191         tasks.add(new SleepyTask("franz", 1000));
192         // task 3 is initially rejected but will eventually succeed
193         Runnable t3 = new SleepyTask("tweety", 1000);
194         tasks.add(t3);
195 
196         // submit tasks
197         LinkedList submitters = this.execute(tasks);
198         assertFalse(submitters.isEmpty());
199 
200         assertFalse(executor.awaitTermination(5000, TimeUnit.MILLISECONDS));
201         assertSame(t3, 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 tasks = new ArrayList();
216         // task 1 runs immediately
217         tasks.add(new SleepyTask("hans", 1000));
218         // task 2 is queued
219         tasks.add(new SleepyTask("franz", 1000));
220         // task 3 is initially rejected & will retry but should fail quickly
221         Runnable failedTask = new SleepyTask("tweety", 1000);
222         tasks.add(failedTask);
223 
224         // submit tasks
225         LinkedList submitters = this.execute(tasks);
226         assertFalse(submitters.isEmpty());
227 
228         // give failure a chance
229         Thread.sleep(failureInterval * 2);
230 
231         LinkedList exceptions = threadGroup.collectedExceptions();
232         assertEquals(1, exceptions.size());
233 
234         Map.Entry threadFailure = (Map.Entry)((Map)(exceptions.getFirst())).entrySet().iterator().next();
235         assertEquals(submitters.getLast(), threadFailure.getKey());
236         assertEquals(RejectedExecutionException.class, threadFailure.getValue().getClass());
237 
238         executor.shutdown();
239         assertTrue(executor.awaitTermination(2500, TimeUnit.MILLISECONDS));
240         assertSame(failedTask, policy.lastRejectedRunnable());
241         assertEquals(0, SleepyTask.activeTasks.get());
242     }
243 
244 }
245 
246 class LastRejectedWaitPolicy extends WaitPolicy
247 {
248     // needed to hand the last rejected Runnable back to the TestCase
249     private volatile Runnable _rejected;
250 
251     public LastRejectedWaitPolicy()
252     {
253         super();
254     }
255 
256     public LastRejectedWaitPolicy(long time, TimeUnit timeUnit)
257     {
258         super(time, timeUnit);
259     }
260 
261     public Runnable lastRejectedRunnable()
262     {
263         return _rejected;
264     }
265 
266     // @Override
267     public void rejectedExecution(Runnable r, ThreadPoolExecutor e)
268     {
269         _rejected = r;
270         super.rejectedExecution(r, e);
271     }
272 }
273 
274 // task to execute - just sleeps for the given interval
275 class SleepyTask extends Object implements Runnable
276 {
277     public static final AtomicInteger activeTasks = new AtomicInteger(0);
278 
279     private final String name;
280     private final long sleepTime;
281 
282     public SleepyTask(String name, long sleepTime)
283     {
284         if (StringUtils.isEmpty(name))
285         {
286             throw new IllegalArgumentException("SleepyTask needs a name!");
287         }
288 
289         this.name = name;
290         this.sleepTime = sleepTime;
291     }
292 
293     public String toString()
294     {
295         return this.getClass().getName() + '{' + name + ", " + sleepTime + '}';
296     }
297 
298     public void run()
299     {
300         activeTasks.incrementAndGet();
301 
302         try
303         {
304             Thread.sleep(sleepTime);
305         }
306         catch (InterruptedException iex)
307         {
308             Thread.currentThread().interrupt();
309         }
310         finally
311         {
312             activeTasks.decrementAndGet();
313         }
314     }
315 
316 }
317 
318 // ThreadGroup wrapper that collects uncaught exceptions
319 class ExceptionCollectingThreadGroup extends ThreadGroup
320 {
321     private final LinkedList/* <Map<Thread, Throwable>> */exceptions = new LinkedList();
322 
323     public ExceptionCollectingThreadGroup()
324     {
325         super("ExceptionCollectingThreadGroup");
326     }
327 
328     // collected Map(Thread, Throwable) associations
329     public LinkedList collectedExceptions()
330     {
331         return exceptions;
332     }
333 
334     // all uncaught Thread exceptions end up here
335     // @Override
336     public void uncaughtException(Thread t, Throwable e)
337     {
338         synchronized (exceptions)
339         {
340             exceptions.add(Collections.singletonMap(t, e));
341         }
342     }
343 }