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.tcp.issues;
8   
9   import org.mule.tck.junit4.AbstractMuleTestCase;
10  
11  import java.io.IOException;
12  import java.net.BindException;
13  import java.net.InetSocketAddress;
14  import java.net.ServerSocket;
15  import java.net.Socket;
16  
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.junit.Test;
20  
21  /**
22   * Can we avoid the "address already in use" errors by using SO_REUSEADDR?
23   *
24   * Typical results are
25  <pre>
26   [07-24 19:32:49] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats without reuse and a pause of 100 ms
27   [07-24 19:33:49] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 57.3 +/- 33.15131973240282
28   [07-24 19:33:49] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats with reuse and a pause of 100 ms
29   [07-24 19:35:32] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 100.0 +/- 0.0
30   [07-24 19:35:32] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats without reuse and a pause of 10 ms
31   [07-24 19:35:48] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 96.8 +/- 7.332121111929359
32   [07-24 19:35:48] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats with reuse and a pause of 10 ms
33   [07-24 19:36:04] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 100.0 +/- 0.0
34   [07-24 19:36:04] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats without reuse and a pause of 1 ms
35   [07-24 19:36:10] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 75.8 +/- 37.690317058894586
36   [07-24 19:36:10] INFO  ReuseExperimentMule2067TestCase [main]: Measuring average run length for 100 repeats with reuse and a pause of 1 ms
37   [07-24 19:36:18] INFO  ReuseExperimentMule2067TestCase [main]: Average run length: 100.0 +/- 0.0
38  </pre>
39   * which suggest that enabling address re-use could help with the issue.
40   *
41   * Note that if a single socket (ie a single port number) is reused for all tests we often
42   * zeroes eveywhere (even with waits of 2sec and similar between iterations/tests).  This
43   * suggests that once the error occurs, the socket enters a long-lived "broken" state.
44   *
45   * All this is by AC on linux, dual CPU, Java 1.4 - I suspect results will vary like crazy
46   * in different contexts.
47   */
48  public class ReuseExperimentMule2067TestCase extends AbstractMuleTestCase
49  {
50  
51      private static final int NO_WAIT = -1;
52      private static final int PORT = 65432;
53      private static boolean NO_REUSE = false;
54      private static boolean REUSE = true;
55  
56      private Log logger = LogFactory.getLog(getClass());
57  
58      @Test
59      public void testReuse() throws IOException
60      {
61          repeatOpenCloseClientServer(1000, 10, PORT, 1, REUSE, false); // fails, but less often?
62          repeatOpenCloseClientServer(100, 10, PORT, 1, NO_REUSE, false); // intermittent
63      }
64  
65      @Test
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                 synchronized(this)
157                 {
158                     if (pause > 0)
159                     {
160                         this.wait(pause);
161                     }
162                 }
163             }
164             catch (InterruptedException e)
165             {
166                 // ignore
167             }
168         }
169     }
170 
171     protected static class Server implements Runnable
172     {
173 
174         private Log logger = LogFactory.getLog(getClass());
175         private ServerSocket server;
176 
177         public Server(int port, boolean reuse) throws IOException
178         {
179             server = new ServerSocket();
180             server.setReuseAddress(reuse);
181             server.bind(new InetSocketAddress("localhost", port));
182         }
183 
184         public void run()
185         {
186             try
187             {
188                 while (true)
189                 {
190                     Socket socket = server.accept();
191                     socket.close();
192                 }
193             }
194             catch (Exception e)
195             {
196                 logger.debug("Expected - dirty closedown: " + e);
197             }
198         }
199 
200         public void close() throws IOException
201         {
202             server.close();
203             server = null;
204         }
205     }
206 
207 }