View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.transport;
8   
9   import org.mule.api.MuleEvent;
10  import org.mule.api.MuleException;
11  import org.mule.api.config.ThreadingProfile;
12  import org.mule.api.endpoint.OutboundEndpoint;
13  import org.mule.api.transport.DispatchException;
14  import org.mule.api.transport.MessageDispatcher;
15  import org.mule.config.ImmutableThreadingProfile;
16  import org.mule.tck.junit4.AbstractMuleContextTestCase;
17  import org.mule.tck.testmodels.mule.TestConnector;
18  import org.mule.tck.testmodels.mule.TestMessageDispatcher;
19  import org.mule.tck.testmodels.mule.TestMessageDispatcherFactory;
20  
21  import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
22  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
23  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;
24  import org.junit.Test;
25  
26  import static org.junit.Assert.assertEquals;
27  import static org.junit.Assert.assertTrue;
28  
29  /**
30   * This test case tests the both dispatcher threading profile and it's rejection
31   * handlers and AbstractConnector dispatch logic by dispatch events using
32   * TestConnector with varying threading profile configurations and asserting the
33   * correct outcome. See: MULE-4752
34   */
35  public class DispatcherThreadingProfileTestCase extends AbstractMuleContextTestCase
36  {
37  
38      public static int DELAY_TIME = 500;
39      public static int WAIT_TIME = DELAY_TIME + DELAY_TIME / 4;
40      public static int SERIAL_WAIT_TIME = (DELAY_TIME * 2) + DELAY_TIME / 4;
41      public static int LONGER_WAIT_TIME = DELAY_TIME * 5;
42      private CountDownLatch latch;
43      private AtomicInteger counter = new AtomicInteger();
44  
45      public DispatcherThreadingProfileTestCase()
46      {
47          setStartContext(true);
48      }
49  
50      @Override
51      protected void doTearDown() throws Exception
52      {
53          super.doTearDown();
54          counter.set(0);
55      }
56  
57      @Test
58      public void testDefaultThreadingProfileConfiguration() throws MuleException
59      {
60          TestConnector connector = new TestConnector(muleContext);
61          muleContext.getRegistry().registerConnector(connector);
62          assertEquals(ThreadingProfile.DEFAULT_MAX_THREADS_ACTIVE, connector.getDispatcherThreadingProfile()
63              .getMaxThreadsActive());
64          assertEquals(ThreadingProfile.DEFAULT_MAX_THREADS_IDLE, connector.getDispatcherThreadingProfile()
65              .getMaxThreadsIdle());
66          assertEquals(ThreadingProfile.WHEN_EXHAUSTED_RUN, connector.getDispatcherThreadingProfile()
67              .getPoolExhaustedAction());
68          assertEquals(ThreadingProfile.DEFAULT_MAX_BUFFER_SIZE, connector.getDispatcherThreadingProfile()
69              .getMaxBufferSize());
70          assertEquals(ThreadingProfile.DEFAULT_MAX_THREAD_TTL, connector.getDispatcherThreadingProfile()
71              .getThreadTTL());
72          assertEquals(ThreadingProfile.DEFAULT_THREAD_WAIT_TIMEOUT, connector.getDispatcherThreadingProfile()
73              .getThreadWaitTimeout());
74      }
75  
76      @Test
77      public void testDefaultRunExhaustedAction() throws Exception
78      {
79          // Default is RUN.
80          // To concurrent dispatch operations are possible even with
81          // maxActiveThreads=1.
82          // Note: This also tests the fact with RUN here, the dispatcher pool needs to
83          // GROW on demand.
84          latch = new CountDownLatch(2);
85  
86          createTestConnectorWithSingleDispatcherThread(ThreadingProfile.WHEN_EXHAUSTED_RUN);
87          dispatchTwoAsyncEvents();
88  
89          // Both execute complete at the same time and finish shortly after DELAY_TIME
90          assertTrue(latch.await(WAIT_TIME, TimeUnit.MILLISECONDS));
91      }
92  
93      @Test
94      public void testWaitExhaustedAction() throws Exception
95      {
96          // Second job waits in workQueue for first job to complete.
97          latch = new CountDownLatch(2);
98  
99          createTestConnectorWithSingleDispatcherThread(1, ThreadingProfile.WHEN_EXHAUSTED_WAIT,
100             ThreadingProfile.DEFAULT_THREAD_WAIT_TIMEOUT, ThreadingProfile.DEFAULT_MAX_BUFFER_SIZE);
101         dispatchTwoAsyncEvents();
102 
103         // Both execute in serial as the second job wait for the fist job to complete
104         assertTrue(latch.await(SERIAL_WAIT_TIME, TimeUnit.MILLISECONDS));
105     }
106 
107     @Test
108     public void testWaitTimeoutExhaustedAction() throws Exception
109     {
110         // Second job attempts to wait in workQueue but waiting job times out/
111         latch = new CountDownLatch(1);
112 
113         createTestConnectorWithSingleDispatcherThread(ThreadingProfile.WHEN_EXHAUSTED_WAIT);
114         dispatchTwoAsyncEvents();
115 
116         // The job that executes finishes shortly after DELAY_TIME
117         assertTrue(latch.await(WAIT_TIME, TimeUnit.MILLISECONDS));
118 
119         // Wait even longer and ensure the other message isn't executed.
120         Thread.sleep(LONGER_WAIT_TIME);
121         assertEquals(1, counter.get());
122     }
123 
124     @Test
125     public void testAbortExhaustedAction() throws Exception
126     {
127         // Second job is aborted
128         latch = new CountDownLatch(1);
129 
130         createTestConnectorWithSingleDispatcherThread(ThreadingProfile.WHEN_EXHAUSTED_ABORT);
131         dispatchTwoAsyncEvents();
132 
133         // The job that executes finishes shortly after DELAY_TIME
134         assertTrue(latch.await(WAIT_TIME, TimeUnit.MILLISECONDS));
135 
136         // Wait even longer and ensure the other message isn't executed.
137         Thread.sleep(LONGER_WAIT_TIME);
138         assertEquals(1, counter.get());
139     }
140 
141     @Test
142     public void testDiscardExhaustedAction() throws Exception
143     {
144         // Second job is discarded
145         latch = new CountDownLatch(1);
146 
147         createTestConnectorWithSingleDispatcherThread(ThreadingProfile.WHEN_EXHAUSTED_DISCARD);
148         dispatchTwoAsyncEvents();
149 
150         // The job that executes finishes shortly after DELAY_TIME
151         assertTrue(latch.await(WAIT_TIME, TimeUnit.MILLISECONDS));
152 
153         // Wait even longer and ensure the other message isn't executed.
154         Thread.sleep(LONGER_WAIT_TIME);
155         assertEquals(1, counter.get());
156     }
157 
158     @Test
159     public void testDiscardOldestExhaustedAction() throws Exception
160     {
161         // The third job is discarded when the fourth job is submitted
162         // The fourth job (now third) is discarded when the fifth job is submitted.
163         // The fifth job (now third) is discarded when the sixth job is submitted.
164         // Therefore the first, second and sixth jobs are run
165         latch = new CountDownLatch(3);
166 
167         // In order for a LinkedBlockingDeque to be used rather than a
168         // SynchronousQueue there need to be
169         // i) 2+ maxActiveThreads ii) maxBufferSize>0
170         createTestConnectorWithSingleDispatcherThread(2, ThreadingProfile.WHEN_EXHAUSTED_DISCARD_OLDEST,
171             ThreadingProfile.DEFAULT_THREAD_WAIT_TIMEOUT, 1);
172 
173         dispatchTwoAsyncEvents();
174         dispatchTwoAsyncEvents();
175         dispatchTwoAsyncEvents();
176 
177         assertTrue(latch.await(SERIAL_WAIT_TIME, TimeUnit.MILLISECONDS));
178         Thread.sleep(LONGER_WAIT_TIME);
179         assertEquals(3, counter.get());
180     }
181 
182     protected void createTestConnectorWithSingleDispatcherThread(int exhaustedAction) throws MuleException
183     {
184         createTestConnectorWithSingleDispatcherThread(1, exhaustedAction, 1, 1);
185     }
186 
187     protected void createTestConnectorWithSingleDispatcherThread(int threads,
188                                                                  int exhaustedAction,
189                                                                  long waitTimeout,
190                                                                  int maxBufferSize) throws MuleException
191     {
192         TestConnector connector = new TestConnector(muleContext);
193         ThreadingProfile threadingProfile = new ImmutableThreadingProfile(threads, threads, maxBufferSize,
194             ThreadingProfile.DEFAULT_MAX_THREAD_TTL, waitTimeout, exhaustedAction, true, null, null);
195         threadingProfile.setMuleContext(muleContext);
196         connector.setDispatcherThreadingProfile(threadingProfile);
197         muleContext.getRegistry().registerConnector(connector);
198         connector.setDispatcherFactory(new DelayTestMessageDispatcherFactory());
199     }
200 
201     private void dispatchTwoAsyncEvents() throws DispatchException, Exception
202     {
203         OutboundEndpoint endpoint = muleContext.getEndpointFactory().getOutboundEndpoint(
204             "test://test");
205         endpoint.process(getTestEvent("data", endpoint));
206         endpoint.process(getTestEvent("data", endpoint));
207     }
208 
209     public class DelayTestMessageDispatcher extends TestMessageDispatcher
210     {
211         public DelayTestMessageDispatcher(OutboundEndpoint endpoint)
212         {
213             super(endpoint);
214         }
215 
216         @Override
217         protected void doDispatch(MuleEvent event) throws Exception
218         {
219             super.doDispatch(event);
220             Thread.sleep(DELAY_TIME);
221             counter.incrementAndGet();
222             latch.countDown();
223         }
224     }
225 
226     class DelayTestMessageDispatcherFactory extends TestMessageDispatcherFactory
227     {
228         @Override
229         public MessageDispatcher create(OutboundEndpoint endpoint) throws MuleException
230         {
231             return new DelayTestMessageDispatcher(endpoint);
232         }
233     }
234 
235 }