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.socket.nio;
021
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertNotSame;
024import static org.junit.Assert.assertTrue;
025
026import java.net.InetSocketAddress;
027
028import org.apache.mina.core.buffer.IoBuffer;
029import org.apache.mina.core.future.ConnectFuture;
030import org.apache.mina.core.future.WriteFuture;
031import org.apache.mina.core.service.IoHandlerAdapter;
032import org.apache.mina.core.session.ExpiringSessionRecycler;
033import org.apache.mina.core.session.IdleStatus;
034import org.apache.mina.core.session.IoSession;
035import org.apache.mina.util.AvailablePortFinder;
036import org.junit.After;
037import org.junit.Before;
038import org.junit.Test;
039
040/**
041 * Tests if datagram sessions are recycled properly.
042 *
043 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
044 */
045public class DatagramRecyclerTest {
046    private NioDatagramAcceptor acceptor;
047
048    private NioDatagramConnector connector;
049
050    public DatagramRecyclerTest() {
051        // Do nothing
052    }
053
054    @Before
055    public void setUp() throws Exception {
056        acceptor = new NioDatagramAcceptor();
057        connector = new NioDatagramConnector();
058    }
059
060    @After
061    public void tearDown() throws Exception {
062        acceptor.dispose();
063        connector.dispose();
064    }
065
066    @Test
067    public void testDatagramRecycler() throws Exception {
068        int port = AvailablePortFinder.getNextAvailable(1024);
069        ExpiringSessionRecycler recycler = new ExpiringSessionRecycler(1, 1);
070
071        MockHandler acceptorHandler = new MockHandler();
072        MockHandler connectorHandler = new MockHandler();
073
074        acceptor.setHandler(acceptorHandler);
075        acceptor.setSessionRecycler(recycler);
076        acceptor.bind(new InetSocketAddress(port));
077
078        try {
079            connector.setHandler(connectorHandler);
080            ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port));
081            future.awaitUninterruptibly();
082
083            // Write whatever to trigger the acceptor.
084            future.getSession().write(IoBuffer.allocate(1)).awaitUninterruptibly();
085
086            // Close the client-side connection.
087            // This doesn't mean that the acceptor-side connection is also closed.
088            // The life cycle of the acceptor-side connection is managed by the recycler.
089            future.getSession().close(true);
090            future.getSession().getCloseFuture().awaitUninterruptibly();
091            assertTrue(future.getSession().getCloseFuture().isClosed());
092
093            // Wait until the acceptor-side connection is closed.
094            while (acceptorHandler.session == null) {
095                Thread.yield();
096            }
097            acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000);
098
099            // Is it closed?
100            assertTrue(acceptorHandler.session.getCloseFuture().isClosed());
101
102            Thread.sleep(1000);
103
104            assertEquals("CROPSECL", connectorHandler.result.toString());
105            assertEquals("CROPRECL", acceptorHandler.result.toString());
106        } finally {
107            acceptor.unbind();
108        }
109    }
110
111    @Test
112    public void testCloseRequest() throws Exception {
113        int port = AvailablePortFinder.getNextAvailable(1024);
114        ExpiringSessionRecycler recycler = new ExpiringSessionRecycler(10, 1);
115
116        MockHandler acceptorHandler = new MockHandler();
117        MockHandler connectorHandler = new MockHandler();
118
119        acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 1);
120        acceptor.setHandler(acceptorHandler);
121        acceptor.setSessionRecycler(recycler);
122        acceptor.bind(new InetSocketAddress(port));
123
124        try {
125            connector.setHandler(connectorHandler);
126            ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port));
127            future.awaitUninterruptibly();
128
129            // Write whatever to trigger the acceptor.
130            future.getSession().write(IoBuffer.allocate(1)).awaitUninterruptibly();
131
132            // Make sure the connection is closed before recycler closes it.
133            while (acceptorHandler.session == null) {
134                Thread.yield();
135            }
136            acceptorHandler.session.close(true);
137            assertTrue(acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000));
138
139            IoSession oldSession = acceptorHandler.session;
140
141            // Wait until all events are processed and clear the state.
142            long startTime = System.currentTimeMillis();
143            while (acceptorHandler.result.length() < 8) {
144                Thread.yield();
145                if (System.currentTimeMillis() - startTime > 5000) {
146                    throw new Exception();
147                }
148            }
149            acceptorHandler.result.setLength(0);
150            acceptorHandler.session = null;
151
152            // Write whatever to trigger the acceptor again.
153            WriteFuture wf = future.getSession().write(IoBuffer.allocate(1)).awaitUninterruptibly();
154            assertTrue(wf.isWritten());
155
156            // Make sure the connection is closed before recycler closes it.
157            while (acceptorHandler.session == null) {
158                Thread.yield();
159            }
160            acceptorHandler.session.close(true);
161            assertTrue(acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000));
162
163            future.getSession().close(true).awaitUninterruptibly();
164
165            assertNotSame(oldSession, acceptorHandler.session);
166        } finally {
167            acceptor.unbind();
168        }
169    }
170
171    private class MockHandler extends IoHandlerAdapter {
172        public volatile IoSession session;
173
174        public final StringBuffer result = new StringBuffer();
175
176        /**
177         * Default constructor
178         */
179        public MockHandler() {
180            super();
181        }
182
183        @Override
184        public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
185            this.session = session;
186            result.append("CA");
187        }
188
189        @Override
190        public void messageReceived(IoSession session, Object message) throws Exception {
191            this.session = session;
192            result.append("RE");
193        }
194
195        @Override
196        public void messageSent(IoSession session, Object message) throws Exception {
197            this.session = session;
198            result.append("SE");
199        }
200
201        @Override
202        public void sessionClosed(IoSession session) throws Exception {
203            this.session = session;
204            result.append("CL");
205        }
206
207        @Override
208        public void sessionCreated(IoSession session) throws Exception {
209            this.session = session;
210            result.append("CR");
211        }
212
213        @Override
214        public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
215            this.session = session;
216            result.append("ID");
217        }
218
219        @Override
220        public void sessionOpened(IoSession session) throws Exception {
221            this.session = session;
222            result.append("OP");
223        }
224    }
225}