1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.constructs.asynchronous;
18  
19  import junit.framework.TestCase;
20  import net.sf.ehcache.CacheException;
21  import net.sf.ehcache.Ehcache;
22  import net.sf.ehcache.StopWatch;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  /**
30   * Tests of the package.
31   *
32   * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
33   * @version $Id: AsynchronousCommandExecutorTest.java 512 2007-07-10 09:18:45Z gregluck $
34   */
35  public class AsynchronousCommandExecutorTest extends TestCase {
36  
37      private static final Log LOG = LogFactory.getLog(AsynchronousCommandExecutorTest.class.getName());
38  
39      private static List messages = new ArrayList();
40  
41      /**
42       * This counter is accessed by 50 threads, thus it is marked volatile so the VM takes more care
43       * incrementing it.
44       */
45      private volatile int counter;
46  
47  
48      private Ehcache messageCache;
49  
50      /**
51       * Does setup tasks
52       *
53       * @throws Exception
54       */
55      protected void setUp() throws Exception {
56          messages = new ArrayList();
57          AsynchronousCommandExecutor.getInstance().setUnsafeDispatcherThreadIntervalSeconds(2);
58          messageCache = AsynchronousCommandExecutor.getInstance().getMessageCache();
59          messageCache.removeAll();
60          messages.clear();
61      }
62  
63      /**
64       * Getter
65       */
66      public static List getMessages() {
67          return messages;
68      }
69  
70  
71      /**
72       * Send a test message to the Topic
73       */
74      public String appendTextMessage() throws Exception {
75          ListAppenderCommand command = new ListAppenderCommand("test", 0, null);
76          return AsynchronousCommandExecutor.getInstance().queueForExecution(command);
77      }
78  
79  
80      /**
81       * Send a non-string Java serializable message to the Topic. Should be treated as an Object Message
82       *
83       *
84       */
85      public String sendSerializableMessage() throws AsynchronousCommandException, CacheException, InterruptedException {
86          ListAppenderCommand command = new ListAppenderCommand(new IsSerializable(), 0, null);
87          return AsynchronousCommandExecutor.getInstance().queueForExecution(command);
88      }
89  
90      /**
91       * A negative test which normally fails trying to send a Java non-serializable message to the Topic.
92       * <p/>
93       * The message will cause an exception at one of two places depending on timing:
94       * <ol>
95       * <li>DiskStore spoolThreadMain - if the element overflows to the disk cache
96       * <li>SynchronousPublisher publishMessage - if the dispatcherThread picks it up to publish
97       * </ol>
98       */
99      public String sendNonSerializableMessage() throws Exception {
100         ListAppenderCommand command = new ListAppenderCommand(new NonSerializable(), 0, null);
101         return AsynchronousCommandExecutor.getInstance().queueForExecution(command);
102     }
103 
104     private void waitForProcessing() throws InterruptedException {
105         Thread.sleep((long) (1700 + (200 * StopWatch.getSpeedAdjustmentFactor())));
106     }
107 
108     private void assertCommandsInCache(int number) throws CacheException {
109         assertEquals(number - 1, messageCache.getSize());
110     }
111 
112     private void assertNoCommandsInCache() throws CacheException {
113         assertEquals(1, messageCache.getSize());
114     }
115 
116 
117 
118     /**
119      * Send a non-string Java serializable message to the Topic. Should be treated as an Object Message
120      *
121      *
122      */
123     public void testSendSerializableMessage() throws AsynchronousCommandException, CacheException, InterruptedException {
124         ListAppenderCommand command = new ListAppenderCommand(new IsSerializable(), 0, null);
125         AsynchronousCommandExecutor.getInstance().queueForExecution(command);
126         waitForProcessing();
127         assertEquals(1, messages.size());
128         assertNoCommandsInCache();
129     }
130 
131     /**
132      * Can we send messages asynchronously? Do they all get through and is the cache empty at the end?
133      */
134     public void testAsynchronousSerializableCommandExecution() throws Exception {
135         ListAppenderCommand command = new ListAppenderCommand(new IsSerializable(), 10, null);
136 
137         for (int i = 0; i < 12; i++) {
138             AsynchronousCommandExecutor.getInstance().queueForExecution(command);
139         }
140         waitForProcessing();
141         assertEquals(12, messages.size());
142         assertNoCommandsInCache();
143     }
144 
145     /**
146      * Can we send messages asynchronously? Do they all get through and is the cache empty at the end?
147      */
148     public void testThreadingAsynchronousSerializableCommandExecution() throws Exception {
149         ListAppenderCommand command = new ListAppenderCommand(new IsSerializable(), 0, null);
150 
151         for (int i = 0; i < 12; i++) {
152             AsynchronousCommandExecutor.getInstance().queueForExecution(command);
153         }
154         int count = 0;
155         while ((count = AsynchronousCommandExecutor.getInstance().countCachedPublishCommands()) != 0) {
156             LOG.info("waiting: count is: " + count);
157         }
158         waitForProcessing();
159         assertEquals(12, messages.size());
160         assertNoCommandsInCache();
161     }
162 
163     /**
164      * Demonstrates the behaviour with bad messages. Each should be tried three times before the next message
165      * is tried. This will take the thread interval which is overriden for testing purposes to 1 second * the
166      * number of messages * 3 repeats + 1 = 16 seconds.
167      */
168     public void testAsynchronousNonSerializableCommandExecution() throws Exception {
169         for (int i = 0; i < 12; i++) {
170             sendNonSerializableMessage();
171         }
172         waitForProcessing();
173         assertEquals(0, messages.size());
174     }
175 
176 
177     /**
178      * Show that, no matter how long the dispatcher thread interval is, messages will attempt to
179      * be sent immediately.
180      * <p/>
181      * This will not work if the thread does polling. It must be able to be woken up when new messages
182      * arrive for processing.
183      */
184     public void testMessageSentImmediately() throws Exception {
185         //Set the interval high and then wait for the initial 1 second interval to complete.
186         AsynchronousCommandExecutor.getInstance().setDispatcherThreadIntervalSeconds(1000000);
187         Thread.sleep(6000);
188         sendSerializableMessage();
189         Thread.sleep(2000);
190         sendSerializableMessage();
191         //Wait for messages to be sent.
192         Thread.sleep(2000);
193         //Will not work unless we were able to wake the thread up.
194         assertEquals(2, messages.size());
195         assertNoCommandsInCache();
196     }
197 
198 
199     /**
200      * Tests that good messages still go through, and in the right order.
201      *
202      * @throws Exception
203      */
204     public void testMixOfGoodAndBadMessages() throws Exception {
205 
206         for (int i = 0; i < 2; i++) {
207             sendSerializableMessage();
208             //sendNonSerializableMessage();
209         }
210         //Wait for messages to be sent.
211         Thread.sleep(9000);
212         assertEquals(2, messages.size());
213         assertNoCommandsInCache();
214 
215     }
216 
217     /**
218      * Should ignore attempts to send messages if they are not due.
219      */
220     public void testMessagesNotRetriedBeforeAllowed() throws Exception {
221         ListAppenderCommand command = new ListAppenderCommand(new IsSerializable(), 0, Exception.class);
222         AsynchronousCommandExecutor commandExecutor = AsynchronousCommandExecutor.getInstance();
223         String uid = commandExecutor.queueForExecution(command);
224         Thread.sleep(4000);
225 
226         int attempts = commandExecutor.getExecuteAttemptsForCommand(uid);
227         //All attempts after the first one should be ignored and not even registered
228         assertEquals(1, attempts);
229         //Should still be in cache
230         assertEquals(2, messageCache.getSize());
231     }
232 
233 
234 
235     /**
236      * Multi-threaded command load/stability test
237      * <p/>
238      * 50 threads use the executor at the same time. We check that all messages came through
239      * and no errors occurred;
240      */
241     public void testConcurrentExecutors() throws Exception {
242 
243         // Run a set of threads that get, put and remove an entry
244         final List executables = new ArrayList();
245         for (int i = 0; i < 50; i++) {
246             final Executable executable = new Executable() {
247                 public void execute() throws Exception {
248                     sendSerializableMessage();
249                     counter++;
250                 }
251             };
252             executables.add(executable);
253         }
254 
255         runThreads(executables);
256         waitForProcessing();
257         int count = messages.size();
258         assertEquals(counter, count);
259 
260     }
261 
262 
263 
264         /**
265      * Runs a set of threads, for a fixed amount of time.
266      */
267     protected void runThreads(final List executables) throws Exception {
268 
269         final long endTime = System.currentTimeMillis() + 10000;
270         final Throwable[] errors = new Throwable[1];
271 
272         // Spin up the threads
273         final Thread[] threads = new Thread[executables.size()];
274         for (int i = 0; i < threads.length; i++) {
275             final Executable executable = (Executable) executables.get(i);
276             threads[i] = new Thread() {
277                 public void run() {
278                     try {
279                         // Run the thread until the given end time
280                         while (System.currentTimeMillis() < endTime) {
281                             executable.execute();
282                             Thread.sleep(2000);
283                         }
284                     } catch (Throwable t) {
285                         // Hang on to any errors
286                         errors[0] = t;
287                     }
288                 }
289             };
290             threads[i].start();
291 
292         }
293 
294         long time = (long) System.currentTimeMillis();
295 
296         // Wait for the threads to finish
297         int maximumWait = 30000;
298         for (int i = 0; i < threads.length; i++) {
299             threads[i].join(maximumWait);
300             if (System.currentTimeMillis() >= time + maximumWait) {
301                 LOG.error("Killed Threads after timeout");
302             }
303         }
304 
305         // Throw any error that happened
306         if (errors[0] != null) {
307             throw new Exception("Test thread failed.", errors[0]);
308         }
309     }
310 
311     /**
312      * A simple command pattern implementation, to allow single methods to be encapsulated and executed.
313      */
314     protected interface Executable {
315         /**
316          * Executes this object.
317          */
318         void execute() throws Exception;
319     }
320 
321 
322 }