1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.transport;
21  
22  import java.io.IOException;
23  import java.net.SocketAddress;
24  import java.util.Collection;
25  
26  import junit.framework.TestCase;
27  
28  import org.apache.mina.core.buffer.IoBuffer;
29  import org.apache.mina.core.future.ConnectFuture;
30  import org.apache.mina.core.service.IoAcceptor;
31  import org.apache.mina.core.service.IoConnector;
32  import org.apache.mina.core.service.IoHandlerAdapter;
33  import org.apache.mina.core.session.IdleStatus;
34  import org.apache.mina.core.session.IoSession;
35  import org.apache.mina.transport.socket.DatagramAcceptor;
36  import org.apache.mina.transport.socket.DatagramSessionConfig;
37  import org.apache.mina.transport.socket.SocketAcceptor;
38  import org.apache.mina.transport.socket.SocketSessionConfig;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * Tests {@link IoAcceptor} resource leakage by repeating bind and unbind.
44   *
45   * @author The Apache MINA Project (dev@mina.apache.org)
46   */
47  public abstract class AbstractBindTest extends TestCase {
48      protected final IoAcceptor acceptor;
49  
50      protected int port;
51  
52      public AbstractBindTest(IoAcceptor acceptor) {
53          this.acceptor = acceptor;
54      }
55  
56      protected abstract SocketAddress createSocketAddress(int port);
57  
58      protected abstract int getPort(SocketAddress address);
59  
60      protected abstract IoConnector newConnector();
61  
62      protected void bind(boolean reuseAddress) throws IOException {
63          acceptor.setHandler(new EchoProtocolHandler());
64  
65          setReuseAddress(reuseAddress);
66  
67          // Find an available test port and bind to it.
68          boolean socketBound = false;
69  
70          // Let's start from port #1 to detect possible resource leak
71          // because test will fail in port 1-1023 if user run this test
72          // as a normal user.
73          for (port = 1; port <= 65535; port++) {
74              socketBound = false;
75              try {
76                  acceptor.setDefaultLocalAddress(createSocketAddress(port));
77                  acceptor.bind();
78                  socketBound = true;
79                  break;
80              } catch (IOException e) {
81                  //System.out.println(e.getMessage());
82              }
83          }
84  
85          // If there is no port available, test fails.
86          if (!socketBound) {
87              throw new IOException("Cannot bind any test port.");
88          }
89  
90          //System.out.println( "Using port " + port + " for testing." );
91      }
92  
93      private void setReuseAddress(boolean reuseAddress) {
94          if (acceptor instanceof DatagramAcceptor) {
95              ((DatagramSessionConfig) acceptor.getSessionConfig())
96                      .setReuseAddress(reuseAddress);
97          } else if (acceptor instanceof SocketAcceptor) {
98              ((SocketAcceptor) acceptor).setReuseAddress(reuseAddress);
99          }
100     }
101 
102     @Override
103     public void tearDown() {
104         try {
105             acceptor.dispose();
106         } catch (Exception e) {
107             // ignore
108         }
109 
110         acceptor.setDefaultLocalAddress(null);
111     }
112 
113     public void testAnonymousBind() throws Exception {
114         acceptor.setHandler(new IoHandlerAdapter());
115         acceptor.setDefaultLocalAddress(null);
116         acceptor.bind();
117         assertNotNull(acceptor.getLocalAddress());
118         acceptor.unbind(acceptor.getLocalAddress());
119         assertNull(acceptor.getLocalAddress());
120         acceptor.setDefaultLocalAddress(createSocketAddress(0));
121         acceptor.bind();
122         assertNotNull(acceptor.getLocalAddress());
123         assertTrue(getPort(acceptor.getLocalAddress()) != 0);
124         acceptor.unbind(acceptor.getLocalAddress());
125     }
126 
127     public void testDuplicateBind() throws IOException {
128         bind(false);
129 
130         try {
131             acceptor.bind();
132             fail("Exception is not thrown");
133         } catch (Exception e) {
134             // Signifies a successfull test case execution
135             assertTrue(true);
136         }
137     }
138 
139     public void testDuplicateUnbind() throws IOException {
140         bind(false);
141 
142         // this should succeed
143         acceptor.unbind();
144 
145         // this shouldn't fail
146         acceptor.unbind();
147     }
148 
149     public void testManyTimes() throws IOException {
150         bind(true);
151 
152         for (int i = 0; i < 1024; i++) {
153             acceptor.unbind();
154             acceptor.bind();
155         }
156     }
157 
158     public void testUnbindDisconnectsClients() throws Exception {
159         bind(true);
160         IoConnector connector = newConnector();
161         IoSession[] sessions = new IoSession[5];
162         connector.setHandler(new IoHandlerAdapter());
163         for (int i = 0; i < sessions.length; i++) {
164             ConnectFuture future = connector.connect(createSocketAddress(port));
165             future.awaitUninterruptibly();
166             sessions[i] = future.getSession();
167             assertTrue(sessions[i].isConnected());
168             assertTrue(sessions[i].write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten());
169         }
170 
171         // Wait for the server side sessions to be created.
172         Thread.sleep(500);
173 
174         Collection<IoSession> managedSessions = acceptor.getManagedSessions().values();
175         assertEquals(5, managedSessions.size());
176 
177         acceptor.unbind();
178 
179         // Wait for the client side sessions to close.
180         Thread.sleep(500);
181 
182         assertEquals(0, managedSessions.size());
183         for (IoSession element : managedSessions) {
184             assertFalse(element.isConnected());
185         }
186     }
187 
188     public void testUnbindResume() throws Exception {
189         bind(true);
190         IoConnector connector = newConnector();
191         IoSession session = null;
192         connector.setHandler(new IoHandlerAdapter());
193         
194         ConnectFuture future = connector.connect(createSocketAddress(port));
195         future.awaitUninterruptibly();
196         session = future.getSession();
197         assertTrue(session.isConnected());
198         assertTrue(session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten());
199 
200         // Wait for the server side session to be created.
201         Thread.sleep(500);
202 
203         Collection<IoSession> managedSession = acceptor.getManagedSessions().values();
204         assertEquals(1, managedSession.size());
205 
206         acceptor.unbind();
207 
208         // Wait for the client side sessions to close.
209         Thread.sleep(500);
210 
211         assertEquals(0, managedSession.size());
212         for (IoSession element : managedSession) {
213             assertFalse(element.isConnected());
214         }
215         
216         // Rebind
217         bind(true);
218         
219         // Check again the connection
220         future = connector.connect(createSocketAddress(port));
221         future.awaitUninterruptibly();
222         session = future.getSession();
223         assertTrue(session.isConnected());
224         assertTrue(session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten());
225 
226         // Wait for the server side session to be created.
227         Thread.sleep(500);
228 
229         managedSession = acceptor.getManagedSessions().values();
230         assertEquals(1, managedSession.size());
231     }
232 
233     public void _testRegressively() throws IOException {
234         setReuseAddress(true);
235 
236         SocketAddress addr = createSocketAddress(port);
237         EchoProtocolHandler handler = new EchoProtocolHandler();
238         acceptor.setDefaultLocalAddress(addr);
239         acceptor.setHandler(handler);
240         for (int i = 0; i < 1048576; i++) {
241             acceptor.bind();
242             acceptor.unbind();
243             //if (i % 100 == 0) {
244                 //System.out.println(i + " (" + new Date() + ")");
245             //}
246         }
247         bind(false);
248     }
249 
250     private static class EchoProtocolHandler extends IoHandlerAdapter {
251         private static final Logger LOG = LoggerFactory
252                 .getLogger(EchoProtocolHandler.class);
253 
254         /**
255          * Default constructor
256          */
257         public EchoProtocolHandler() {
258             super();
259         }
260         
261         @Override
262         public void sessionCreated(IoSession session) {
263             if (session.getConfig() instanceof SocketSessionConfig) {
264                 ((SocketSessionConfig) session.getConfig())
265                         .setReceiveBufferSize(2048);
266             }
267 
268             session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
269         }
270 
271         @Override
272         public void sessionIdle(IoSession session, IdleStatus status) {
273             LOG.info("*** IDLE #" + session.getIdleCount(IdleStatus.BOTH_IDLE)
274                     + " ***");
275         }
276 
277         @Override
278         public void exceptionCaught(IoSession session, Throwable cause) {
279             //cause.printStackTrace();
280             session.close(true);
281         }
282 
283         @Override
284         public void messageReceived(IoSession session, Object message)
285                 throws Exception {
286             if (!(message instanceof IoBuffer)) {
287                 return;
288             }
289 
290             IoBuffer rb = (IoBuffer) message;
291             // Write the received data back to remote peer
292             IoBuffer wb = IoBuffer.allocate(rb.remaining());
293             wb.put(rb);
294             wb.flip();
295             session.write(wb);
296         }
297     }
298 }