001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.transport; 021 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertFalse; 024import static org.junit.Assert.assertNotNull; 025import static org.junit.Assert.assertNull; 026import static org.junit.Assert.assertTrue; 027import static org.junit.Assert.fail; 028 029import java.io.IOException; 030import java.net.SocketAddress; 031import java.util.Collection; 032 033import org.apache.mina.core.buffer.IoBuffer; 034import org.apache.mina.core.future.ConnectFuture; 035import org.apache.mina.core.service.IoAcceptor; 036import org.apache.mina.core.service.IoConnector; 037import org.apache.mina.core.service.IoHandlerAdapter; 038import org.apache.mina.core.session.IdleStatus; 039import org.apache.mina.core.session.IoSession; 040import org.apache.mina.transport.socket.DatagramAcceptor; 041import org.apache.mina.transport.socket.DatagramSessionConfig; 042import org.apache.mina.transport.socket.SocketAcceptor; 043import org.apache.mina.transport.socket.SocketSessionConfig; 044import org.junit.After; 045import org.junit.Ignore; 046import org.junit.Test; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050/** 051 * Tests {@link IoAcceptor} resource leakage by repeating bind and unbind. 052 * 053 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 054 */ 055public abstract class AbstractBindTest { 056 protected final IoAcceptor acceptor; 057 058 protected int port; 059 060 public AbstractBindTest(IoAcceptor acceptor) { 061 this.acceptor = acceptor; 062 } 063 064 protected abstract SocketAddress createSocketAddress(int port); 065 066 protected abstract int getPort(SocketAddress address); 067 068 protected abstract IoConnector newConnector(); 069 070 protected void bind(boolean reuseAddress) throws IOException { 071 acceptor.setHandler(new EchoProtocolHandler()); 072 073 setReuseAddress(reuseAddress); 074 075 // Find an available test port and bind to it. 076 boolean socketBound = false; 077 078 // Let's start from port #1 to detect possible resource leak 079 // because test will fail in port 1-1023 if user run this test 080 // as a normal user. 081 for (port = 1024; port <= 65535; port++) { 082 socketBound = false; 083 try { 084 acceptor.setDefaultLocalAddress(createSocketAddress(port)); 085 acceptor.bind(); 086 socketBound = true; 087 break; 088 } catch (IOException e) { 089 //System.out.println(e.getMessage()); 090 } 091 } 092 093 // If there is no port available, test fails. 094 if (!socketBound) { 095 throw new IOException("Cannot bind any test port."); 096 } 097 098 //System.out.println( "Using port " + port + " for testing." ); 099 } 100 101 private void setReuseAddress(boolean reuseAddress) { 102 if (acceptor instanceof DatagramAcceptor) { 103 ((DatagramSessionConfig) acceptor.getSessionConfig()).setReuseAddress(reuseAddress); 104 } else if (acceptor instanceof SocketAcceptor) { 105 ((SocketAcceptor) acceptor).setReuseAddress(reuseAddress); 106 } 107 } 108 109 @After 110 public void tearDown() { 111 try { 112 acceptor.dispose(); 113 } catch (Exception e) { 114 // ignore 115 } 116 117 acceptor.setDefaultLocalAddress(null); 118 } 119 120 @Test 121 public void testAnonymousBind() throws Exception { 122 acceptor.setHandler(new IoHandlerAdapter()); 123 acceptor.setDefaultLocalAddress(null); 124 acceptor.bind(); 125 assertNotNull(acceptor.getLocalAddress()); 126 acceptor.unbind(acceptor.getLocalAddress()); 127 assertNull(acceptor.getLocalAddress()); 128 acceptor.setDefaultLocalAddress(createSocketAddress(0)); 129 acceptor.bind(); 130 assertNotNull(acceptor.getLocalAddress()); 131 assertTrue(getPort(acceptor.getLocalAddress()) != 0); 132 acceptor.unbind(acceptor.getLocalAddress()); 133 } 134 135 @Test 136 public void testDuplicateBind() throws IOException { 137 bind(false); 138 139 try { 140 acceptor.bind(); 141 fail("Exception is not thrown"); 142 } catch (Exception e) { 143 // Signifies a successfull test case execution 144 assertTrue(true); 145 } 146 } 147 148 @Test 149 public void testDuplicateUnbind() throws IOException { 150 bind(false); 151 152 // this should succeed 153 acceptor.unbind(); 154 155 // this shouldn't fail 156 acceptor.unbind(); 157 } 158 159 @Test 160 public void testManyTimes() throws IOException { 161 bind(true); 162 163 for (int i = 0; i < 1024; i++) { 164 acceptor.unbind(); 165 acceptor.bind(); 166 } 167 } 168 169 @Test 170 public void testUnbindDisconnectsClients() throws Exception { 171 bind(true); 172 IoConnector connector = newConnector(); 173 IoSession[] sessions = new IoSession[5]; 174 connector.setHandler(new IoHandlerAdapter()); 175 for (int i = 0; i < sessions.length; i++) { 176 ConnectFuture future = connector.connect(createSocketAddress(port)); 177 future.awaitUninterruptibly(); 178 sessions[i] = future.getSession(); 179 assertTrue(sessions[i].isConnected()); 180 assertTrue(sessions[i].write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten()); 181 } 182 183 // Wait for the server side sessions to be created. 184 Thread.sleep(500); 185 186 Collection<IoSession> managedSessions = acceptor.getManagedSessions().values(); 187 assertEquals(5, managedSessions.size()); 188 189 acceptor.unbind(); 190 191 // Wait for the client side sessions to close. 192 Thread.sleep(500); 193 194 assertEquals(0, managedSessions.size()); 195 for (IoSession element : managedSessions) { 196 assertFalse(element.isConnected()); 197 } 198 } 199 200 @Test 201 public void testUnbindResume() throws Exception { 202 bind(true); 203 IoConnector connector = newConnector(); 204 IoSession session = null; 205 connector.setHandler(new IoHandlerAdapter()); 206 207 ConnectFuture future = connector.connect(createSocketAddress(port)); 208 future.awaitUninterruptibly(); 209 session = future.getSession(); 210 assertTrue(session.isConnected()); 211 assertTrue(session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten()); 212 213 // Wait for the server side session to be created. 214 Thread.sleep(500); 215 216 Collection<IoSession> managedSession = acceptor.getManagedSessions().values(); 217 assertEquals(1, managedSession.size()); 218 219 acceptor.unbind(); 220 221 // Wait for the client side sessions to close. 222 Thread.sleep(500); 223 224 assertEquals(0, managedSession.size()); 225 for (IoSession element : managedSession) { 226 assertFalse(element.isConnected()); 227 } 228 229 // Rebind 230 bind(true); 231 232 // Check again the connection 233 future = connector.connect(createSocketAddress(port)); 234 future.awaitUninterruptibly(); 235 session = future.getSession(); 236 assertTrue(session.isConnected()); 237 assertTrue(session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten()); 238 239 // Wait for the server side session to be created. 240 Thread.sleep(500); 241 242 managedSession = acceptor.getManagedSessions().values(); 243 assertEquals(1, managedSession.size()); 244 } 245 246 @Test 247 @Ignore 248 public void testRegressively() throws IOException { 249 setReuseAddress(true); 250 251 SocketAddress addr = createSocketAddress(port); 252 EchoProtocolHandler handler = new EchoProtocolHandler(); 253 acceptor.setDefaultLocalAddress(addr); 254 acceptor.setHandler(handler); 255 for (int i = 0; i < 1048576; i++) { 256 acceptor.bind(); 257 acceptor.unbind(); 258 } 259 bind(false); 260 } 261 262 private static class EchoProtocolHandler extends IoHandlerAdapter { 263 private static final Logger LOG = LoggerFactory.getLogger(EchoProtocolHandler.class); 264 265 /** 266 * Default constructor 267 */ 268 public EchoProtocolHandler() { 269 super(); 270 } 271 272 @Override 273 public void sessionCreated(IoSession session) { 274 if (session.getConfig() instanceof SocketSessionConfig) { 275 ((SocketSessionConfig) session.getConfig()).setReceiveBufferSize(2048); 276 } 277 278 session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); 279 } 280 281 @Override 282 public void sessionIdle(IoSession session, IdleStatus status) { 283 LOG.info("*** IDLE #" + session.getIdleCount(IdleStatus.BOTH_IDLE) + " ***"); 284 } 285 286 @Override 287 public void exceptionCaught(IoSession session, Throwable cause) { 288 //cause.printStackTrace(); 289 session.close(true); 290 } 291 292 @Override 293 public void messageReceived(IoSession session, Object message) throws Exception { 294 if (!(message instanceof IoBuffer)) { 295 return; 296 } 297 298 IoBuffer rb = (IoBuffer) message; 299 // Write the received data back to remote peer 300 IoBuffer wb = IoBuffer.allocate(rb.remaining()); 301 wb.put(rb); 302 wb.flip(); 303 session.write(wb); 304 } 305 } 306}