1   /*
2    * $Id: ReuseExperimentMule2067TestCase.java 10489 2008-01-23 17:53:38Z dfeist $
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.transport.tcp.issues;
12  
13  import org.mule.tck.AbstractMuleTestCase;
14  
15  import java.io.IOException;
16  import java.net.BindException;
17  import java.net.InetSocketAddress;
18  import java.net.ServerSocket;
19  import java.net.Socket;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  /**
25   * Can we avoid the "address already in use" errors by using SO_REUSEADDR?
26   *
27   * Typical results are
28  <pre>
29   [07-24 19:32:49] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats without reuse and a pause of 100 ms
30   [07-24 19:33:49] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 57.3 +/- 33.15131973240282
31   [07-24 19:33:49] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats with reuse and a pause of 100 ms
32   [07-24 19:35:32] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 100.0 +/- 0.0
33   [07-24 19:35:32] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats without reuse and a pause of 10 ms
34   [07-24 19:35:48] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 96.8 +/- 7.332121111929359
35   [07-24 19:35:48] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats with reuse and a pause of 10 ms
36   [07-24 19:36:04] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 100.0 +/- 0.0
37   [07-24 19:36:04] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats without reuse and a pause of 1 ms
38   [07-24 19:36:10] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 75.8 +/- 37.690317058894586
39   [07-24 19:36:10] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats with reuse and a pause of 1 ms
40   [07-24 19:36:18] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 100.0 +/- 0.0
41  </pre>
42   * which suggest that enabling address re-use could help with the issue.
43   *
44   * Note that if a single socket (ie a single port number) is reused for all tests we often
45   * zeroes eveywhere (even with waits of 2sec and similar between iterations/tests).  This
46   * suggests that once the error occurs, the socket enters a long-lived "broken" state.
47   *
48   * All this is by AC on linux, dual CPU, Java 1.4 - I suspect results will vary like crazy
49   * in different contexts.
50   */
51  public class ReuseExperimentMule2067TestCase extends AbstractMuleTestCase
52  {
53  
54      private static final int NO_WAIT = -1;
55      private static final int PORT = 65432;
56      private static boolean NO_REUSE = false;
57      private static boolean REUSE = true;
58  
59      private Log logger = LogFactory.getLog(getClass());
60  
61      public void testReuse() throws IOException
62      {
63          repeatOpenCloseClientServer(1000, 10, PORT, 1, REUSE, false); // fails, but less often?
64          repeatOpenCloseClientServer(100, 10, PORT, 1, NO_REUSE, false); // intermittent
65      }
66  
67      public void testMeasureImprovement() throws IOException
68      {
69          measureMeanRunLength(10, 100, 10, PORT, 100, NO_REUSE);
70          measureMeanRunLength(10, 100, 10, PORT+10, 100, REUSE);
71          measureMeanRunLength(10, 100, 10, PORT+20, 10, NO_REUSE);
72          measureMeanRunLength(10, 100, 10, PORT+30, 10, REUSE);
73          measureMeanRunLength(10, 100, 10, PORT+40, 1, NO_REUSE);
74          measureMeanRunLength(10, 100, 10, PORT+50, 1, REUSE);
75      }
76  
77      protected void measureMeanRunLength(int sampleSize, int numberOfRepeats, int numberOfConnections,
78                                          int port, long pause,  boolean reuse)
79              throws IOException
80      {
81          logger.info("Measuring average run length for " + numberOfRepeats + " repeats " +
82                  (reuse ? "with" : "without") + " reuse and a pause of " + pause + " ms");
83          int totalLength = 0;
84          long totalLengthSquared = 0;
85          for (int i = 0; i < sampleSize; ++i)
86          {
87              int length = repeatOpenCloseClientServer(numberOfRepeats, numberOfConnections, port+i, pause, reuse, true);
88              totalLength += length;
89              totalLengthSquared += length * length;
90          }
91          double mean = totalLength / (double) sampleSize;
92          double sd = Math.sqrt(totalLengthSquared / (double) sampleSize - mean * mean);
93          logger.info("Average run length: " + mean + " +/- " + sd);
94      }
95  
96      protected int repeatOpenCloseClientServer(int numberOfRepeats, int numberOfConnections, int port,
97                                                long pause, boolean reuse, boolean noFail)
98              throws IOException
99      {
100         String message = "Repeating openCloseClientServer with pauses of " + pause + " ms "
101                     + (reuse ? "with" : "without") + " reuse";
102         if (noFail)
103         {
104             logger.debug(message);
105         }
106         else
107         {
108             logger.info(message);
109         }
110         for (int i = 0; i < numberOfRepeats; i++)
111         {
112             if (0 != i)
113             {
114                 pause(pause);
115             }
116             try
117             {
118                 openCloseClientServer(numberOfConnections, port, reuse);
119             }
120             catch (BindException e)
121             {
122                 if (noFail && e.getMessage().indexOf("Address already in use") > -1)
123                 {
124                     return i;
125                 }
126                 throw e;
127             }
128         }
129         return numberOfRepeats;
130     }
131 
132     protected void openCloseClientServer(int numberOfConnections, int port, boolean reuse)
133             throws IOException
134     {
135         Server server = new Server(port, reuse);
136         try {
137             new Thread(server).start();
138             for (int i = 0; i < numberOfConnections; i++)
139             {
140                 logger.debug("opening socket " + i);
141                 Socket client = new Socket("localhost", port);
142                 client.close();
143             }
144         }
145         finally
146         {
147             server.close();
148         }
149     }
150 
151     protected void pause(long pause)
152     {
153         if (pause != NO_WAIT)
154         {
155             try
156             {
157                 synchronized(this)
158                 {
159                     if (pause > 0)
160                     {
161                         this.wait(pause);
162                     }
163                 }
164             }
165             catch (InterruptedException e)
166             {
167                 // ignore
168             }
169         }
170     }
171 
172     protected static class Server implements Runnable
173     {
174 
175         private Log logger = LogFactory.getLog(getClass());
176         private ServerSocket server;
177 
178         public Server(int port, boolean reuse) throws IOException
179         {
180             server = new ServerSocket();
181             server.setReuseAddress(reuse);
182             server.bind(new InetSocketAddress("localhost", port));
183         }
184 
185         public void run()
186         {
187             try
188             {
189                 while (true)
190                 {
191                     Socket socket = server.accept();
192                     socket.close();
193                 }
194             }
195             catch (Exception e)
196             {
197                 logger.debug("Expected - dirty closedown: " + e);
198             }
199         }
200 
201         public void close() throws IOException
202         {
203             server.close();
204             server = null;
205         }
206     }
207 
208 }