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}