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