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   * @version $Rev: 755169 $, $Date: 2009-03-17 10:45:33 +0100 (Tue, 17 Mar 2009) $
47   */
48  public abstract class AbstractBindTest extends TestCase {
49      protected final IoAcceptor acceptor;
50  
51      protected int port;
52  
53      public AbstractBindTest(IoAcceptor acceptor) {
54          this.acceptor = acceptor;
55      }
56  
57      protected abstract SocketAddress createSocketAddress(int port);
58  
59      protected abstract int getPort(SocketAddress address);
60  
61      protected abstract IoConnector newConnector();
62  
63      protected void bind(boolean reuseAddress) throws IOException {
64          acceptor.setHandler(new EchoProtocolHandler());
65  
66          setReuseAddress(reuseAddress);
67  
68          // Find an available test port and bind to it.
69          boolean socketBound = false;
70  
71          // Let's start from port #1 to detect possible resource leak
72          // because test will fail in port 1-1023 if user run this test
73          // as a normal user.
74          for (port = 1; port <= 65535; port++) {
75              socketBound = false;
76              try {
77                  acceptor.setDefaultLocalAddress(createSocketAddress(port));
78                  acceptor.bind();
79                  socketBound = true;
80                  break;
81              } catch (IOException e) {
82                  //System.out.println(e.getMessage());
83              }
84          }
85  
86          // If there is no port available, test fails.
87          if (!socketBound) {
88              throw new IOException("Cannot bind any test port.");
89          }
90  
91          //System.out.println( "Using port " + port + " for testing." );
92      }
93  
94      private void setReuseAddress(boolean reuseAddress) {
95          if (acceptor instanceof DatagramAcceptor) {
96              ((DatagramSessionConfig) acceptor.getSessionConfig())
97                      .setReuseAddress(reuseAddress);
98          } else if (acceptor instanceof SocketAcceptor) {
99              ((SocketAcceptor) acceptor).setReuseAddress(reuseAddress);
100         }
101     }
102 
103     @Override
104     public void tearDown() {
105         try {
106             acceptor.dispose();
107         } catch (Exception e) {
108             // ignore
109         }
110 
111         acceptor.setDefaultLocalAddress(null);
112     }
113 
114     public void testAnonymousBind() throws Exception {
115         acceptor.setHandler(new IoHandlerAdapter());
116         acceptor.setDefaultLocalAddress(null);
117         acceptor.bind();
118         assertNotNull(acceptor.getLocalAddress());
119         acceptor.unbind(acceptor.getLocalAddress());
120         assertNull(acceptor.getLocalAddress());
121         acceptor.setDefaultLocalAddress(createSocketAddress(0));
122         acceptor.bind();
123         assertNotNull(acceptor.getLocalAddress());
124         assertTrue(getPort(acceptor.getLocalAddress()) != 0);
125         acceptor.unbind(acceptor.getLocalAddress());
126     }
127 
128     public void testDuplicateBind() throws IOException {
129         bind(false);
130 
131         try {
132             acceptor.bind();
133             fail("Exception is not thrown");
134         } catch (Exception e) {
135             // Signifies a successfull test case execution
136             assertTrue(true);
137         }
138     }
139 
140     public void testDuplicateUnbind() throws IOException {
141         bind(false);
142 
143         // this should succeed
144         acceptor.unbind();
145 
146         // this shouldn't fail
147         acceptor.unbind();
148     }
149 
150     public void testManyTimes() throws IOException {
151         bind(true);
152 
153         for (int i = 0; i < 1024; i++) {
154             acceptor.unbind();
155             acceptor.bind();
156         }
157     }
158 
159     public void testUnbindDisconnectsClients() throws Exception {
160         bind(true);
161         IoConnector connector = newConnector();
162         IoSession[] sessions = new IoSession[5];
163         connector.setHandler(new IoHandlerAdapter());
164         for (int i = 0; i < sessions.length; i++) {
165             ConnectFuture future = connector.connect(createSocketAddress(port));
166             future.awaitUninterruptibly();
167             sessions[i] = future.getSession();
168             assertTrue(sessions[i].isConnected());
169             assertTrue(sessions[i].write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten());
170         }
171 
172         // Wait for the server side sessions to be created.
173         Thread.sleep(500);
174 
175         Collection<IoSession> managedSessions = acceptor.getManagedSessions().values();
176         assertEquals(5, managedSessions.size());
177 
178         acceptor.unbind();
179 
180         // Wait for the client side sessions to close.
181         Thread.sleep(500);
182 
183         assertEquals(0, managedSessions.size());
184         for (IoSession element : managedSessions) {
185             assertFalse(element.isConnected());
186         }
187     }
188 
189     public void testUnbindResume() throws Exception {
190         bind(true);
191         IoConnector connector = newConnector();
192         IoSession session = null;
193         connector.setHandler(new IoHandlerAdapter());
194         
195         ConnectFuture future = connector.connect(createSocketAddress(port));
196         future.awaitUninterruptibly();
197         session = future.getSession();
198         assertTrue(session.isConnected());
199         assertTrue(session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten());
200 
201         // Wait for the server side session to be created.
202         Thread.sleep(500);
203 
204         Collection<IoSession> managedSession = acceptor.getManagedSessions().values();
205         assertEquals(1, managedSession.size());
206 
207         acceptor.unbind();
208 
209         // Wait for the client side sessions to close.
210         Thread.sleep(500);
211 
212         assertEquals(0, managedSession.size());
213         for (IoSession element : managedSession) {
214             assertFalse(element.isConnected());
215         }
216         
217         // Rebind
218         bind(true);
219         
220         // Check again the connection
221         future = connector.connect(createSocketAddress(port));
222         future.awaitUninterruptibly();
223         session = future.getSession();
224         assertTrue(session.isConnected());
225         assertTrue(session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten());
226 
227         // Wait for the server side session to be created.
228         Thread.sleep(500);
229 
230         managedSession = acceptor.getManagedSessions().values();
231         assertEquals(1, managedSession.size());
232     }
233 
234     public void _testRegressively() throws IOException {
235         setReuseAddress(true);
236 
237         SocketAddress addr = createSocketAddress(port);
238         EchoProtocolHandler handler = new EchoProtocolHandler();
239         acceptor.setDefaultLocalAddress(addr);
240         acceptor.setHandler(handler);
241         for (int i = 0; i < 1048576; i++) {
242             acceptor.bind();
243             acceptor.unbind();
244             //if (i % 100 == 0) {
245                 //System.out.println(i + " (" + new Date() + ")");
246             //}
247         }
248         bind(false);
249     }
250 
251     private static class EchoProtocolHandler extends IoHandlerAdapter {
252         private static final Logger log = LoggerFactory
253                 .getLogger(EchoProtocolHandler.class);
254 
255         @Override
256         public void sessionCreated(IoSession session) {
257             if (session.getConfig() instanceof SocketSessionConfig) {
258                 ((SocketSessionConfig) session.getConfig())
259                         .setReceiveBufferSize(2048);
260             }
261 
262             session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
263         }
264 
265         @Override
266         public void sessionIdle(IoSession session, IdleStatus status) {
267             log.info("*** IDLE #" + session.getIdleCount(IdleStatus.BOTH_IDLE)
268                     + " ***");
269         }
270 
271         @Override
272         public void exceptionCaught(IoSession session, Throwable cause) {
273             //cause.printStackTrace();
274             session.close(true);
275         }
276 
277         @Override
278         public void messageReceived(IoSession session, Object message)
279                 throws Exception {
280             if (!(message instanceof IoBuffer)) {
281                 return;
282             }
283 
284             IoBuffer rb = (IoBuffer) message;
285             // Write the received data back to remote peer
286             IoBuffer wb = IoBuffer.allocate(rb.remaining());
287             wb.put(rb);
288             wb.flip();
289             session.write(wb);
290         }
291     }
292 }